refactor(mobile): separate database package (#3809)

This commit is contained in:
Stephen Zhou 2025-05-26 14:48:27 +08:00 committed by GitHub
parent 9094581f0a
commit fb40cbea93
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
99 changed files with 434 additions and 119 deletions

View File

@ -23,6 +23,7 @@
"@expo/react-native-action-sheet": "4.1.1",
"@follow/components": "workspace:*",
"@follow/constants": "workspace:*",
"@follow/database": "workspace:*",
"@follow/hooks": "workspace:*",
"@follow/legal": "workspace:*",
"@follow/models": "workspace:*",

View File

@ -1,4 +1,5 @@
import type { FeedViewType } from "@follow/constants"
import type { MediaModel } from "@follow/database/src/schemas/types"
import { useEffect, useMemo, useState } from "react"
import { ScrollView, View } from "react-native"
import Animated, {
@ -9,7 +10,6 @@ import Animated, {
} from "react-native-reanimated"
import { Galeria } from "@/src/components/ui/image/galeria"
import type { MediaModel } from "@/src/database/schemas/types"
import { EntryGridFooter } from "@/src/modules/entry-content/EntryGridFooter"
import { Image } from "../image/Image"

View File

@ -1,8 +1,8 @@
import type { FeedViewType } from "@follow/constants"
import type { FeedSchema } from "@follow/database/src/schemas/types"
import type { ReactNode } from "react"
import { useCallback, useMemo, useState } from "react"
import type { FeedSchema } from "@/src/database/schemas/types"
import { getFeedIconSource } from "@/src/lib/image"
import type { ImageProps } from "../image/Image"

View File

@ -1,24 +1,5 @@
import type { ExpoSQLiteDatabase } from "drizzle-orm/expo-sqlite"
import { drizzle } from "drizzle-orm/expo-sqlite"
import * as FileSystem from "expo-file-system"
import * as SQLite from "expo-sqlite"
import * as schema from "./schemas"
export const sqlite = SQLite.openDatabaseSync("follow.db")
let db: ExpoSQLiteDatabase<typeof schema> & {
$client: SQLite.SQLiteDatabase
}
export function initializeDb() {
db = drizzle(sqlite, {
schema,
logger: false,
})
}
export { db }
export const getDbPath = () => {
return `${FileSystem.documentDirectory}SQLite/follow.db`
}

View File

@ -1,7 +1,7 @@
import { initializeDb } from "@follow/database/src/db"
import { tracker } from "@follow/tracker"
import { nativeApplicationVersion } from "expo-application"
import { initializeDb } from "../database"
import { settingSyncQueue } from "../modules/settings/sync-queue"
import { initAnalytics } from "./analytics"
import { initializeAppCheck } from "./app-check"

View File

@ -1,10 +1,6 @@
import { migrate } from "drizzle-orm/expo-sqlite/migrator"
import { migrateDb } from "@follow/database/src/db"
import { useSyncExternalStore } from "react"
import migrations from "@/drizzle/migrations"
import { db } from "../database"
let storeChangeFn: () => void
const subscribe = (onStoreChange: () => void) => {
storeChangeFn = onStoreChange
@ -20,7 +16,7 @@ const migrateStore = {
export const migrateDatabase = async () => {
try {
await migrate(db, migrations)
await migrateDb()
migrateStore.success = true
storeChangeFn?.()
} catch (error) {

View File

@ -1,9 +1,8 @@
import type { AttachmentsModel } from "@follow/database/src/schemas/types"
import { atom, useAtom } from "jotai"
import { useCallback, useEffect } from "react"
import TrackPlayer, { State, useActiveTrack, usePlaybackState } from "react-native-track-player"
import type { AttachmentsModel } from "../database/schemas/types"
const LOADING_SUFFIX = "_loading"
export function usePlayingUrl() {

View File

@ -1,4 +1,5 @@
import { FeedViewType } from "@follow/constants"
import type { MediaModel } from "@follow/database/src/schemas/types"
import { tracker } from "@follow/tracker"
import { uniqBy } from "es-toolkit/compat"
import { useMemo } from "react"
@ -7,7 +8,6 @@ import { Text, View } from "react-native"
import { showEntryGaleriaAccessory } from "@/src/components/native/GaleriaAccessory/EntryGaleriaAccessory"
import { preloadWebViewEntry } from "@/src/components/native/webview/EntryContentWebView"
import { MediaCarousel } from "@/src/components/ui/carousel/MediaCarousel"
import type { MediaModel } from "@/src/database/schemas/types"
import { getFeedIconSource } from "@/src/lib/image"
import { useEntry } from "@/src/store/entry/hooks"
import { getFeed } from "@/src/store/feed/getter"

View File

@ -1,4 +1,5 @@
import type { EntrySchema, SubscriptionSchema } from "../database/schemas/types"
import type { EntrySchema, SubscriptionSchema } from "@follow/database/src/schemas/types"
import type { EntryModel } from "../store/entry/types"
import type { SubscriptionModel } from "../store/subscription/store"

View File

@ -1,6 +1,6 @@
import type { FeedViewType } from "@follow/constants"
import type { FeedSchema, InboxSchema } from "@follow/database/src/schemas/types"
import type { FeedSchema, InboxSchema } from "../database/schemas/types"
import type { CollectionModel } from "../store/collection/types"
import type { EntryModel } from "../store/entry/types"
import type { FeedModel } from "../store/feed/types"

View File

@ -1,4 +1,9 @@
import type { EntrySchema, ListSchema, SubscriptionSchema } from "../database/schemas/types"
import type {
EntrySchema,
ListSchema,
SubscriptionSchema,
} from "@follow/database/src/schemas/types"
import type { EntryModel } from "../store/entry/types"
import type { ListModel } from "../store/list/store"
import type { SubscriptionModel } from "../store/subscription/store"

View File

@ -1,4 +1,5 @@
import { ActionSheetProvider } from "@expo/react-native-action-sheet"
import { sqlite } from "@follow/database/src/db"
import { jotaiStore } from "@follow/utils"
import { PortalProvider } from "@gorhom/portal"
import { QueryClientProvider } from "@tanstack/react-query"
@ -13,7 +14,6 @@ import { useCurrentColorsVariants } from "react-native-uikit-colors"
import { ErrorBoundary } from "../components/common/ErrorBoundary"
import { GlobalErrorScreen } from "../components/errors/GlobalErrorScreen"
import { sqlite } from "../database"
import { queryClient } from "../lib/query-client"
import { MigrationProvider } from "./migration"
import { ServerConfigsProvider } from "./ServerConfigsProvider"

View File

@ -1,8 +1,8 @@
import { db } from "@follow/database/src/db"
import { collectionsTable } from "@follow/database/src/schemas"
import type { CollectionSchema } from "@follow/database/src/schemas/types"
import { eq, inArray } from "drizzle-orm"
import { db } from "../database"
import { collectionsTable } from "../database/schemas"
import type { CollectionSchema } from "../database/schemas/types"
import { collectionActions } from "../store/collection/store"
import type { Hydratable, Resetable } from "./internal/base"
import { conflictUpdateAllExcept } from "./internal/utils"

View File

@ -1,9 +1,9 @@
import { db } from "@follow/database/src/db"
import { entriesTable } from "@follow/database/src/schemas"
import type { EntrySchema } from "@follow/database/src/schemas/types"
import { and, between, eq, inArray, or } from "drizzle-orm"
import { getGeneralSettings } from "../atoms/settings/general"
import { db } from "../database"
import { entriesTable } from "../database/schemas"
import type { EntrySchema } from "../database/schemas/types"
import { dbStoreMorph } from "../morph/db-store"
import { entryActions } from "../store/entry/store"
import type { PublishAtTimeRangeFilter } from "../store/unread/types"

View File

@ -1,6 +1,7 @@
import { db } from "../database"
import { feedsTable } from "../database/schemas"
import type { FeedSchema } from "../database/schemas/types"
import { db } from "@follow/database/src/db"
import { feedsTable } from "@follow/database/src/schemas"
import type { FeedSchema } from "@follow/database/src/schemas/types"
import { feedActions } from "../store/feed/store"
import type { Hydratable, Resetable } from "./internal/base"
import { conflictUpdateAllExcept } from "./internal/utils"

View File

@ -1,7 +1,7 @@
import { imagesTable } from "@/src/database/schemas"
import type { ImageSchema } from "@/src/database/schemas/types"
import { db } from "@follow/database/src/db"
import { imagesTable } from "@follow/database/src/schemas"
import type { ImageSchema } from "@follow/database/src/schemas/types"
import { db } from "../database"
import { imageActions } from "../store/image/store"
import type { Hydratable, Resetable } from "./internal/base"
import { conflictUpdateAllExcept } from "./internal/utils"

View File

@ -1,7 +1,7 @@
import { inboxesTable } from "@/src/database/schemas"
import type { InboxSchema } from "@/src/database/schemas/types"
import { db } from "@follow/database/src/db"
import { inboxesTable } from "@follow/database/src/schemas"
import type { InboxSchema } from "@follow/database/src/schemas/types"
import { db } from "../database"
import { inboxActions } from "../store/inbox/store"
import type { Hydratable, Resetable } from "./internal/base"
import { conflictUpdateAllExcept } from "./internal/utils"

View File

@ -1,8 +1,8 @@
import { db } from "@follow/database/src/db"
import { listsTable } from "@follow/database/src/schemas"
import type { ListSchema } from "@follow/database/src/schemas/types"
import { eq } from "drizzle-orm"
import { db } from "../database"
import { listsTable } from "../database/schemas"
import type { ListSchema } from "../database/schemas/types"
import { listActions } from "../store/list/store"
import type { Hydratable, Resetable } from "./internal/base"
import { conflictUpdateAllExcept } from "./internal/utils"

View File

@ -1,9 +1,14 @@
import type { FeedViewType } from "@follow/constants/src/enums"
import { db } from "@follow/database/src/db"
import {
feedsTable,
inboxesTable,
listsTable,
subscriptionsTable,
} from "@follow/database/src/schemas"
import type { SubscriptionSchema } from "@follow/database/src/schemas/types"
import { and, eq, inArray, notInArray, sql } from "drizzle-orm"
import { db } from "../database"
import { feedsTable, inboxesTable, listsTable, subscriptionsTable } from "../database/schemas"
import type { SubscriptionSchema } from "../database/schemas/types"
import { dbStoreMorph } from "../morph/db-store"
import { subscriptionActions } from "../store/subscription/store"
import type { Hydratable, Resetable } from "./internal/base"

View File

@ -1,9 +1,8 @@
import { db } from "@follow/database/src/db"
import { summariesTable } from "@follow/database/src/schemas"
import type { SummarySchema } from "@follow/database/src/schemas/types"
import { eq } from "drizzle-orm"
import { db } from "../database"
import { summariesTable } from "../database/schemas"
import type { SummarySchema } from "../database/schemas/types"
class SummaryServiceStatic {
async insertSummary(data: Omit<SummarySchema, "createdAt">) {
const updateExceptEmpty = Object.fromEntries(

View File

@ -1,8 +1,8 @@
import { db } from "@follow/database/src/db"
import { translationsTable } from "@follow/database/src/schemas"
import type { TranslationSchema } from "@follow/database/src/schemas/types"
import { eq } from "drizzle-orm"
import { db } from "../database"
import { translationsTable } from "../database/schemas"
import type { TranslationSchema } from "../database/schemas/types"
import { translationActions } from "../store/translation/store"
import type { Hydratable, Resetable } from "./internal/base"

View File

@ -1,6 +1,7 @@
import { db } from "../database"
import { unreadTable } from "../database/schemas"
import type { UnreadSchema } from "../database/schemas/types"
import { db } from "@follow/database/src/db"
import { unreadTable } from "@follow/database/src/schemas"
import type { UnreadSchema } from "@follow/database/src/schemas/types"
import { unreadActions } from "../store/unread/store"
import type { UnreadUpdateOptions } from "../store/unread/types"
import type { Hydratable, Resetable } from "./internal/base"

View File

@ -1,8 +1,8 @@
import { db } from "@follow/database/src/db"
import { usersTable } from "@follow/database/src/schemas"
import type { UserSchema } from "@follow/database/src/schemas/types"
import { eq } from "drizzle-orm"
import { db } from "../database"
import { usersTable } from "../database/schemas"
import type { UserSchema } from "../database/schemas/types"
import { userActions } from "../store/user/store"
import type { Hydratable } from "./internal/base"
import { conflictUpdateAllExcept } from "./internal/utils"

View File

@ -1,6 +1,6 @@
import type { FeedViewType } from "@follow/constants"
import type { CollectionSchema } from "@follow/database/src/schemas/types"
import type { CollectionSchema } from "@/src/database/schemas/types"
import { apiClient } from "@/src/lib/api-fetch"
import { CollectionService } from "@/src/services/collection"

View File

@ -1,3 +1,3 @@
import type { CollectionSchema } from "@/src/database/schemas/types"
import type { CollectionSchema } from "@follow/database/src/schemas/types"
export type CollectionModel = CollectionSchema

View File

@ -1,4 +1,4 @@
import type { EntrySchema } from "@/src/database/schemas/types"
import type { EntrySchema } from "@follow/database/src/schemas/types"
import type { EntryTranslation } from "../translation/types"

View File

@ -1,4 +1,4 @@
import type { FeedSchema } from "@/src/database/schemas/types"
import type { FeedSchema } from "@follow/database/src/schemas/types"
import { useFeedStore } from "./store"

View File

@ -1,4 +1,5 @@
import type { FeedSchema } from "@/src/database/schemas/types"
import type { FeedSchema } from "@follow/database/src/schemas/types"
import { apiClient } from "@/src/lib/api-fetch"
import { FeedService } from "@/src/services/feed"

View File

@ -1,4 +1,4 @@
import type { FeedSchema } from "@/src/database/schemas/types"
import type { FeedSchema } from "@follow/database/src/schemas/types"
export type FeedModel = FeedSchema & {
nonce?: string

View File

@ -1,6 +1,6 @@
import type { ImageSchema } from "@follow/database/src/schemas/types"
import ImageColors from "react-native-image-colors"
import type { ImageSchema } from "@/src/database/schemas/types"
import { ImagesService } from "@/src/services/image"
import { createImmerSetter, createTransaction, createZustandStore } from "../internal/helper"

View File

@ -1,4 +1,5 @@
import type { InboxSchema } from "@/src/database/schemas/types"
import type { InboxSchema } from "@follow/database/src/schemas/types"
import { InboxService } from "@/src/services/inbox"
import { createTransaction, createZustandStore } from "../internal/helper"

View File

@ -1,4 +1,5 @@
import type { ListSchema } from "@/src/database/schemas/types"
import type { ListSchema } from "@follow/database/src/schemas/types"
import { apiClient } from "@/src/lib/api-fetch"
import { honoMorph } from "@/src/morph/hono"
import { storeDbMorph } from "@/src/morph/store-db"

View File

@ -1,7 +1,7 @@
import { FeedViewType } from "@follow/constants"
import type { SubscriptionSchema } from "@follow/database/src/schemas/types"
import { tracker } from "@follow/tracker"
import type { SubscriptionSchema } from "@/src/database/schemas/types"
import { apiClient } from "@/src/lib/api-fetch"
import { toast } from "@/src/lib/toast"
import { honoMorph } from "@/src/morph/hono"

View File

@ -1,5 +1,6 @@
import type { SummarySchema } from "@follow/database/src/schemas/types"
import { getActionLanguage } from "@/src/atoms/settings/general"
import type { SummarySchema } from "@/src/database/schemas/types"
import { apiClient } from "@/src/lib/api-fetch"
import { summaryService } from "@/src/services/summary"

View File

@ -1,4 +1,5 @@
import type { TranslationSchema } from "@/src/database/schemas/types"
import type { TranslationSchema } from "@follow/database/src/schemas/types"
import { apiClient } from "@/src/lib/api-fetch"
import type { SupportedLanguages } from "@/src/lib/language"
import { checkLanguage } from "@/src/lib/translation"

View File

@ -1,7 +1,7 @@
import type { FeedViewType } from "@follow/constants"
import type { UnreadSchema } from "@follow/database/src/schemas/types"
import { getGeneralSettings } from "@/src/atoms/settings/general"
import type { UnreadSchema } from "@/src/database/schemas/types"
import { apiClient } from "@/src/lib/api-fetch"
import { setBadgeCountAsyncWithPermission } from "@/src/lib/permission"
import { EntryService } from "@/src/services/entry"

View File

@ -1,7 +1,7 @@
import { UserRole } from "@follow/constants"
import type { UserSchema } from "@follow/database/src/schemas/types"
import type { AuthSession } from "@follow/shared"
import type { UserSchema } from "@/src/database/schemas/types"
import { apiClient } from "@/src/lib/api-fetch"
import { changeEmail, sendVerificationEmail, twoFactor, updateUser } from "@/src/lib/auth"
import { toast } from "@/src/lib/toast"

View File

@ -13,6 +13,7 @@
"noEmit": true,
"strict": true,
"noUncheckedIndexedAccess": true,
"noImplicitOverride": true
"noImplicitOverride": true,
"allowJs": true
}
}

View File

@ -0,0 +1,8 @@
import { defineConfig } from "drizzle-kit"
export default defineConfig({
dialect: "sqlite",
driver: "expo",
schema: "./src/schemas/index.ts",
out: "./src/drizzle",
})

View File

@ -0,0 +1,36 @@
{
"name": "@follow/database",
"type": "module",
"private": true,
"author": "Folo Team",
"license": "GPL-3.0-only",
"homepage": "https://github.com/RSSNext",
"repository": {
"url": "https://github.com/RSSNext/follow",
"type": "git"
},
"sideEffects": false,
"exports": {
"./src/*": {
"types": "./src/*.ts",
"require": "./src/*.ts",
"import": "./src/*.ts"
}
},
"scripts": {
"generate": "drizzle-kit generate",
"typecheck": "tsc --noEmit"
},
"dependencies": {
"@follow/constants": "workspace:*",
"@follow/models": "workspace:*",
"@follow/shared": "workspace:*",
"drizzle-orm": "0.42.0",
"expo-sqlite": "15.2.9",
"sqlocal": "0.14.1"
},
"devDependencies": {
"@follow/configs": "workspace:*",
"drizzle-kit": "0.31.0"
}
}

View File

@ -0,0 +1 @@
export const SQLITE_DB_NAME = "follow.db"

View File

@ -0,0 +1,24 @@
import type { SqliteRemoteDatabase } from "drizzle-orm/sqlite-proxy"
import { drizzle } from "drizzle-orm/sqlite-proxy"
import { SQLocalDrizzle } from "sqlocal/drizzle"
import { SQLITE_DB_NAME } from "./constant"
import migrations from "./drizzle/migrations"
import { migrate } from "./migrator"
import * as schema from "./schemas"
export const sqlite = new SQLocalDrizzle(SQLITE_DB_NAME)
let db: SqliteRemoteDatabase<typeof schema>
export function initializeDb() {
db = drizzle(sqlite.driver, sqlite.batchDriver, {
schema,
logger: false,
})
}
export { db }
export function migrateDb() {
return migrate(db, migrations)
}

View File

@ -0,0 +1,26 @@
import type { ExpoSQLiteDatabase } from "drizzle-orm/expo-sqlite"
import { drizzle } from "drizzle-orm/expo-sqlite"
import { migrate } from "drizzle-orm/expo-sqlite/migrator"
import * as SQLite from "expo-sqlite"
import { SQLITE_DB_NAME } from "./constant"
import migrations from "./drizzle/migrations"
import * as schema from "./schemas"
export const sqlite = SQLite.openDatabaseSync(SQLITE_DB_NAME)
let db: ExpoSQLiteDatabase<typeof schema> & {
$client: SQLite.SQLiteDatabase
}
export function initializeDb() {
db = drizzle(sqlite, {
schema,
logger: false,
})
}
export { db }
export function migrateDb(): Promise<void> {
return migrate(db, migrations)
}

View File

@ -0,0 +1,6 @@
import type { DB } from "./types"
export declare const sqlite: unknown
export declare const db: DB
export declare function initializeDb(): void
export declare function migrateDb(): Promise<void>

View File

@ -0,0 +1,98 @@
import { sql } from "drizzle-orm"
import type { SqliteRemoteDatabase } from "drizzle-orm/sqlite-proxy"
interface MigrationConfig {
journal: MigrationJournal
migrations: Record<string, string>
migrationsTable?: string
}
interface MigrationJournal {
version: string
dialect: string
entries: {
idx: number
version: string
when: number
tag: string
breakpoints: boolean
}[]
}
interface MigrationMeta {
sql: string[]
folderMillis: number
hash: string
bps: boolean
}
// https://github.com/drizzle-team/drizzle-orm/blob/main/drizzle-orm/src/expo-sqlite/migrator.ts
async function readMigrationFiles({
journal,
migrations,
}: MigrationConfig): Promise<MigrationMeta[]> {
const migrationQueries: MigrationMeta[] = []
for await (const journalEntry of journal.entries) {
const query = migrations[`m${journalEntry.idx.toString().padStart(4, "0")}`]
if (!query) {
throw new Error(`Missing migration: ${journalEntry.tag}`)
}
try {
const result = query.split("--> statement-breakpoint").map((it) => {
return it
})
migrationQueries.push({
sql: result,
bps: journalEntry.breakpoints,
folderMillis: journalEntry.when,
hash: "",
})
} catch {
throw new Error(`Failed to parse migration: ${journalEntry.tag}`)
}
}
return migrationQueries
}
// https://github.com/drizzle-team/drizzle-orm/blob/main/drizzle-orm/src/sqlite-proxy/migrator.ts
export async function migrate<TSchema extends Record<string, unknown>>(
db: SqliteRemoteDatabase<TSchema>,
config: MigrationConfig,
) {
const migrations = await readMigrationFiles(config)
const migrationTableCreate = sql`
CREATE TABLE IF NOT EXISTS "__drizzle_migrations" (
id SERIAL PRIMARY KEY,
hash text NOT NULL,
created_at numeric
)
`
await db.run(migrationTableCreate)
const dbMigrations = await db.values<[number, string, string]>(
sql`SELECT id, hash, created_at FROM "__drizzle_migrations" ORDER BY created_at DESC LIMIT 1`,
)
const lastDbMigration = dbMigrations[0] ?? undefined
const queriesToRun: string[] = []
for (const migration of migrations) {
if (!lastDbMigration || Number(lastDbMigration[2])! < migration.folderMillis) {
queriesToRun.push(
...migration.sql,
`INSERT INTO "__drizzle_migrations" ("hash", "created_at") VALUES('${migration.hash}', '${migration.folderMillis}')`,
)
}
}
for (const query of queriesToRun) {
await db.run(sql.raw(query))
}
}

View File

@ -1,16 +1,10 @@
import type { FeedViewType } from "@follow/constants"
import type { ActionSettings } from "@follow/models/src/types"
import type { SupportedActionLanguage } from "@follow/shared/src/language"
import { sql } from "drizzle-orm"
import { integer, sqliteTable, text, uniqueIndex } from "drizzle-orm/sqlite-core"
import type { SupportedLanguages } from "@/src/lib/language"
import type {
ActionSettings,
AttachmentsModel,
ExtraModel,
ImageColorsResult,
MediaModel,
} from "./types"
import type { AttachmentsModel, ExtraModel, ImageColorsResult, MediaModel } from "./types"
export const feedsTable = sqliteTable("feeds", {
id: text("id").primaryKey(),
@ -120,7 +114,7 @@ export const translationsTable = sqliteTable(
"translations",
(t) => ({
entryId: t.text("entry_id").notNull().primaryKey(),
language: t.text("language").$type<SupportedLanguages>().notNull(),
language: t.text("language").$type<SupportedActionLanguage>().notNull(),
title: t.text("title"),
description: t.text("description"),
content: t.text("content"),

View File

@ -1,5 +1,3 @@
import type { HonoApiClient } from "@/src/morph/types"
import type {
collectionsTable,
entriesTable,
@ -36,8 +34,6 @@ export type TranslationSchema = typeof translationsTable.$inferInsert
export type ImageSchema = typeof imagesTable.$inferInsert
export type ActionSettings = HonoApiClient.ActionSettings
export type MediaModel = {
url: string
type: "photo" | "video"
@ -63,4 +59,37 @@ export type ExtraModel = {
}[]
}
export { ImageColorsResult } from "react-native-image-colors"
// export { ImageColorsResult } from "react-native-image-colors"
interface AndroidImageColors {
dominant: string
average: string
vibrant: string
darkVibrant: string
lightVibrant: string
darkMuted: string
lightMuted: string
muted: string
platform: "android"
}
interface WebImageColors {
dominant: string
vibrant: string
darkVibrant: string
lightVibrant: string
darkMuted: string
lightMuted: string
muted: string
platform: "web"
}
interface IOSImageColors {
background: string
primary: string
secondary: string
detail: string
platform: "ios"
}
export type ImageColorsResult = AndroidImageColors | IOSImageColors | WebImageColors

View File

@ -0,0 +1,7 @@
import type { BaseSQLiteDatabase } from "drizzle-orm/sqlite-core/db"
import type * as schema from "./schemas"
export type DB =
| BaseSQLiteDatabase<"async", any, typeof schema>
| BaseSQLiteDatabase<"sync", any, typeof schema>

View File

@ -0,0 +1,7 @@
{
"extends": "@follow/configs/tsconfig.extend.json",
"compilerOptions": {
"types": ["vite/client"]
},
"include": ["src/**/*", "drizzle.config.ts"]
}

View File

@ -11,6 +11,11 @@
"import": "./src/rsshub.ts",
"types": "./src/rsshub.ts"
},
"./src/*": {
"types": "./src/*.ts",
"require": "./src/*.ts",
"import": "./src/*.ts"
},
"./types": {
"import": "./src/types.ts",
"require": "./src/types.ts"

View File

@ -62,6 +62,8 @@ export type EntriesResponse = Array<
| Exclude<Awaited<ReturnType<typeof _apiClient.entries.inbox.$post>>["data"], undefined>
>[number]
export type ActionSettings = Exclude<EntriesResponse[number]["settings"], undefined>
export type CombinedEntryModel = Omit<EntriesResponse[number], "feeds"> & {
entries: {
content?: string | null
@ -88,6 +90,7 @@ export type DataResponse<T> = {
data?: T
}
type Nullable<T> = T | null | undefined
export type ActiveEntryId = Nullable<string>
export type SubscriptionModel = ExtractBizResponse<

View File

@ -25,6 +25,11 @@
"types": "./src/interface/*.ts",
"require": "./src/interface/*.ts",
"import": "./src/interface/*.ts"
},
"./src/*": {
"types": "./src/*.ts",
"require": "./src/*.ts",
"import": "./src/*.ts"
}
},
"main": "./exports.ts",

View File

@ -763,6 +763,9 @@ importers:
'@follow/constants':
specifier: workspace:*
version: link:../../packages/internal/constants
'@follow/database':
specifier: workspace:*
version: link:../../packages/internal/database
'@follow/hooks':
specifier: workspace:*
version: link:../../packages/internal/hooks
@ -1565,6 +1568,34 @@ importers:
specifier: workspace:*
version: link:../types
packages/internal/database:
dependencies:
'@follow/constants':
specifier: workspace:*
version: link:../constants
'@follow/models':
specifier: workspace:*
version: link:../models
'@follow/shared':
specifier: workspace:*
version: link:../shared
drizzle-orm:
specifier: 0.42.0
version: 0.42.0(@opentelemetry/api@1.9.0)(@types/pg@8.6.1)(expo-sqlite@15.2.9(expo@53.0.4(@babel/core@7.26.10)(@expo/metro-runtime@5.0.4(react-native@0.79.1(@babel/core@7.26.10)(@types/react@19.1.3)(bufferutil@4.0.9)(react@19.0.0)))(bufferutil@4.0.9)(graphql@16.8.1)(react-native-webview@13.13.5(react-native@0.79.1(@babel/core@7.26.10)(@types/react@19.1.3)(bufferutil@4.0.9)(react@19.0.0))(react@19.0.0))(react-native@0.79.1(@babel/core@7.26.10)(@types/react@19.1.3)(bufferutil@4.0.9)(react@19.0.0))(react@19.0.0))(react-native@0.79.1(@babel/core@7.26.10)(@types/react@19.1.3)(bufferutil@4.0.9)(react@19.0.0))(react@19.0.0))(kysely@0.27.6)
expo-sqlite:
specifier: 15.2.9
version: 15.2.9(expo@53.0.4(@babel/core@7.26.10)(@expo/metro-runtime@5.0.4(react-native@0.79.1(@babel/core@7.26.10)(@types/react@19.1.3)(bufferutil@4.0.9)(react@19.0.0)))(bufferutil@4.0.9)(graphql@16.8.1)(react-native-webview@13.13.5(react-native@0.79.1(@babel/core@7.26.10)(@types/react@19.1.3)(bufferutil@4.0.9)(react@19.0.0))(react@19.0.0))(react-native@0.79.1(@babel/core@7.26.10)(@types/react@19.1.3)(bufferutil@4.0.9)(react@19.0.0))(react@19.0.0))(react-native@0.79.1(@babel/core@7.26.10)(@types/react@19.1.3)(bufferutil@4.0.9)(react@19.0.0))(react@19.0.0)
sqlocal:
specifier: 0.14.1
version: 0.14.1(bufferutil@4.0.9)(drizzle-orm@0.42.0(@opentelemetry/api@1.9.0)(@types/pg@8.6.1)(expo-sqlite@15.2.9(expo@53.0.4(@babel/core@7.26.10)(@expo/metro-runtime@5.0.4(react-native@0.79.1(@babel/core@7.26.10)(@types/react@19.1.3)(bufferutil@4.0.9)(react@19.0.0)))(bufferutil@4.0.9)(graphql@16.8.1)(react-native-webview@13.13.5(react-native@0.79.1(@babel/core@7.26.10)(@types/react@19.1.3)(bufferutil@4.0.9)(react@19.0.0))(react@19.0.0))(react-native@0.79.1(@babel/core@7.26.10)(@types/react@19.1.3)(bufferutil@4.0.9)(react@19.0.0))(react@19.0.0))(react-native@0.79.1(@babel/core@7.26.10)(@types/react@19.1.3)(bufferutil@4.0.9)(react@19.0.0))(react@19.0.0))(kysely@0.27.6))(kysely@0.27.6)
devDependencies:
'@follow/configs':
specifier: workspace:*
version: link:../../configs
drizzle-kit:
specifier: 0.31.0
version: 0.31.0
packages/internal/hooks:
dependencies:
'@follow/shared':
@ -5901,6 +5932,10 @@ packages:
'@sinonjs/fake-timers@10.3.0':
resolution: {integrity: sha512-V4BG07kuYSUkTCSBHG8G8TNhM+F19jXFWnQtzj+we8DrkpSBCee9Z3Ms8yiGer/dlmhe35/Xdgyo3/0rQKg7YA==}
'@sqlite.org/sqlite-wasm@3.49.1-build4':
resolution: {integrity: sha512-TBbTTWhiI6v2CT7J1hij5shx+RGL4iICprVGYhO+LKv5Nbn3NeJPWCY8kMKL5vA6b33NeWkBk4dy6RFbNh3jBw==}
hasBin: true
'@standard-schema/utils@0.3.0':
resolution: {integrity: sha512-e7Mew686owMaPJVNNLs55PUvgz371nKgwsc4vxE49zsODpJEnxgxRo2y/OKrqueavXgZNMDVj3DdHFlaSAeU8g==}
@ -6258,6 +6293,9 @@ packages:
'@ungap/structured-clone@1.3.0':
resolution: {integrity: sha512-WmoN8qaIAo7WTYWbAZuG8PYEhn5fkz7dZrqTBZ7dtt//lL2Gwms1IcnQ5yHqjDfX8Ft5j4YzDM23f87zBfDe9g==}
'@ungap/with-resolvers@0.1.0':
resolution: {integrity: sha512-g7f0IkJdPW2xhY7H4iE72DAsIyfuwEFc6JWc2tYFwKDMWWAF699vGjrM348cwQuOXgHpe1gWFe+Eiyjx/ewvvw==}
'@unocss/config@66.1.2':
resolution: {integrity: sha512-2sQXj+Qaq4RVDELVTPoXMggZ30g1WKHeCuur396I12Ab0HgAR6bTc/DIrNtqKVHFI3mmlvP1oM1ynhKWSKPsTg==}
engines: {node: '>=14'}
@ -7398,6 +7436,9 @@ packages:
code-inspector-plugin@0.20.10:
resolution: {integrity: sha512-G3aQ+t65N+rJlydPRUoG4vegjQb3seitCXCuNICUMhkDLetdVONLTASePVPCADv+fXl0vyW0hnZzAAxb9UnwOQ==}
coincident@1.2.3:
resolution: {integrity: sha512-Uxz3BMTWIslzeWjuQnizGWVg0j6khbvHUQ8+5BdM7WuJEm4ALXwq3wluYoB+uF68uPBz/oUOeJnYURKyfjexlA==}
collapse-white-space@2.1.0:
resolution: {integrity: sha512-loKTxY1zCOuG4j9f6EPnuyyYkf58RnhhWTvRoZEokgB+WbdXehfjFviyOVYkqzEWz1Q5kRiZdBYS5SwxbQYwzw==}
@ -9558,6 +9599,9 @@ packages:
resolution: {integrity: sha512-R1fam6D4CyKQGNlvJne4dkNF+PvUUl7TAJInvTGa9fti9qAv95quQz29GXapA4d8Ec266mJJxFVh82M4GIIGDQ==}
engines: {node: '>= 12'}
gc-hook@0.3.1:
resolution: {integrity: sha512-E5M+O/h2o7eZzGhzRZGex6hbB3k4NWqO0eA+OzLRLXxhdbYPajZnynPwAtphnh+cRHPwsj5Z80dqZlfI4eK55A==}
generate-function@2.3.1:
resolution: {integrity: sha512-eeB5GfMNeevm/GRYq20ShmsaGcmI81kIX2K9XQx5miC8KdHaC6Jm0qQ8ZNeGOi7wYB8OsdxKs+Y2oVuTFuVwKQ==}
@ -12571,6 +12615,9 @@ packages:
proxy-from-env@1.1.0:
resolution: {integrity: sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==}
proxy-target@3.0.2:
resolution: {integrity: sha512-FFE1XNwXX/FNC3/P8HiKaJSy/Qk68RitG/QEcLy/bVnTAPlgTAWPZKh0pARLAnpfXQPKyalBhk009NRTgsk8vQ==}
public-encrypt@4.0.3:
resolution: {integrity: sha512-zVpa8oKZSz5bTMTFClc1fQOnyyEzpl5ozpi1B5YcvBrdohMjH2rfsBtyXcuNuwjsDIXmBYlF2N5FlJYhR29t8Q==}
@ -13717,6 +13764,17 @@ packages:
sprintf-js@1.1.3:
resolution: {integrity: sha512-Oo+0REFV59/rz3gfJNKQiBlwfHaSESl1pcGyABQsnnIfWOFt6JNj5gCog2U6MLZ//IGYD+nA8nI+mTShREReaA==}
sqlocal@0.14.1:
resolution: {integrity: sha512-UhmNKWT7WBgJIrvp5JVuKAOOLXww4ZDVBfouPb/CIg/ONSCVPqaXFzwvTZ3vZY6SkfJLw9JCX+dZU+T1/orHmg==}
peerDependencies:
drizzle-orm: '*'
kysely: '*'
peerDependenciesMeta:
drizzle-orm:
optional: true
kysely:
optional: true
ssri@9.0.1:
resolution: {integrity: sha512-o57Wcn66jMQvfHG1FlYbWeZWW/dHZhJXjpIcTfXldXEk5nz5lStPo3mK0OJQfGR3RbZUlbISexbljkJzuEj/8Q==}
engines: {node: ^12.13.0 || ^14.15.0 || >=16.0.0}
@ -20106,7 +20164,7 @@ snapshots:
debug: 2.6.9
invariant: 2.2.4
metro: 0.82.4(bufferutil@4.0.9)
metro-config: 0.82.4(bufferutil@4.0.9)
metro-config: 0.82.4(bufferutil@4.0.9)(utf-8-validate@6.0.5)
metro-core: 0.82.4
semver: 7.7.1
transitivePeerDependencies:
@ -20653,6 +20711,8 @@ snapshots:
dependencies:
'@sinonjs/commons': 3.0.1
'@sqlite.org/sqlite-wasm@3.49.1-build4': {}
'@standard-schema/utils@0.3.0': {}
'@stylistic/eslint-plugin@4.4.0(eslint@9.25.1(jiti@2.4.2))(typescript@5.8.3)':
@ -21082,6 +21142,8 @@ snapshots:
'@ungap/structured-clone@1.3.0': {}
'@ungap/with-resolvers@0.1.0': {}
'@unocss/config@66.1.2':
dependencies:
'@unocss/core': 66.1.2
@ -22524,6 +22586,18 @@ snapshots:
transitivePeerDependencies:
- supports-color
coincident@1.2.3(bufferutil@4.0.9):
dependencies:
'@ungap/structured-clone': 1.3.0
'@ungap/with-resolvers': 0.1.0
gc-hook: 0.3.1
proxy-target: 3.0.2
optionalDependencies:
ws: 8.18.2(bufferutil@4.0.9)(utf-8-validate@6.0.5)
transitivePeerDependencies:
- bufferutil
- utf-8-validate
collapse-white-space@2.1.0: {}
color-convert@0.5.3:
@ -25280,6 +25354,8 @@ snapshots:
transitivePeerDependencies:
- supports-color
gc-hook@0.3.1: {}
generate-function@2.3.1:
dependencies:
is-property: 1.0.2
@ -27174,21 +27250,6 @@ snapshots:
transitivePeerDependencies:
- supports-color
metro-config@0.82.4(bufferutil@4.0.9):
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)
metro-cache: 0.82.4
metro-core: 0.82.4
metro-runtime: 0.82.4
transitivePeerDependencies:
- bufferutil
- 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
@ -27203,7 +27264,6 @@ snapshots:
- bufferutil
- supports-color
- utf-8-validate
optional: true
metro-core@0.82.4:
dependencies:
@ -27315,7 +27375,6 @@ snapshots:
- bufferutil
- supports-color
- utf-8-validate
optional: true
metro@0.82.4(bufferutil@4.0.9):
dependencies:
@ -27343,7 +27402,7 @@ snapshots:
metro-babel-transformer: 0.82.4
metro-cache: 0.82.4
metro-cache-key: 0.82.4
metro-config: 0.82.4(bufferutil@4.0.9)
metro-config: 0.82.4(bufferutil@4.0.9)(utf-8-validate@6.0.5)
metro-core: 0.82.4
metro-file-map: 0.82.4
metro-resolver: 0.82.4
@ -27410,7 +27469,6 @@ snapshots:
- bufferutil
- supports-color
- utf-8-validate
optional: true
micromark-core-commonmark@2.0.3:
dependencies:
@ -28791,6 +28849,8 @@ snapshots:
proxy-from-env@1.1.0: {}
proxy-target@3.0.2: {}
public-encrypt@4.0.3:
dependencies:
bn.js: 4.12.2
@ -30218,6 +30278,17 @@ snapshots:
sprintf-js@1.1.3: {}
sqlocal@0.14.1(bufferutil@4.0.9)(drizzle-orm@0.42.0(@opentelemetry/api@1.9.0)(@types/pg@8.6.1)(expo-sqlite@15.2.9(expo@53.0.4(@babel/core@7.26.10)(@expo/metro-runtime@5.0.4(react-native@0.79.1(@babel/core@7.26.10)(@types/react@19.1.3)(bufferutil@4.0.9)(react@19.0.0)))(bufferutil@4.0.9)(graphql@16.8.1)(react-native-webview@13.13.5(react-native@0.79.1(@babel/core@7.26.10)(@types/react@19.1.3)(bufferutil@4.0.9)(react@19.0.0))(react@19.0.0))(react-native@0.79.1(@babel/core@7.26.10)(@types/react@19.1.3)(bufferutil@4.0.9)(react@19.0.0))(react@19.0.0))(react-native@0.79.1(@babel/core@7.26.10)(@types/react@19.1.3)(bufferutil@4.0.9)(react@19.0.0))(react@19.0.0))(kysely@0.27.6))(kysely@0.27.6):
dependencies:
'@sqlite.org/sqlite-wasm': 3.49.1-build4
coincident: 1.2.3(bufferutil@4.0.9)
optionalDependencies:
drizzle-orm: 0.42.0(@opentelemetry/api@1.9.0)(@types/pg@8.6.1)(expo-sqlite@15.2.9(expo@53.0.4(@babel/core@7.26.10)(@expo/metro-runtime@5.0.4(react-native@0.79.1(@babel/core@7.26.10)(@types/react@19.1.3)(bufferutil@4.0.9)(react@19.0.0)))(bufferutil@4.0.9)(graphql@16.8.1)(react-native-webview@13.13.5(react-native@0.79.1(@babel/core@7.26.10)(@types/react@19.1.3)(bufferutil@4.0.9)(react@19.0.0))(react@19.0.0))(react-native@0.79.1(@babel/core@7.26.10)(@types/react@19.1.3)(bufferutil@4.0.9)(react@19.0.0))(react@19.0.0))(react-native@0.79.1(@babel/core@7.26.10)(@types/react@19.1.3)(bufferutil@4.0.9)(react@19.0.0))(react@19.0.0))(kysely@0.27.6)
kysely: 0.27.6
transitivePeerDependencies:
- bufferutil
- utf-8-validate
ssri@9.0.1:
dependencies:
minipass: 3.3.6