feat: db cleaner (#151)
* feat: db cleaner Signed-off-by: Innei <i@innei.in> * chore: add test case Signed-off-by: Innei <i@innei.in> * update Signed-off-by: Innei <i@innei.in> * refactor: data struct Signed-off-by: Innei <i@innei.in> * feat: cleaner old data Signed-off-by: Innei <i@innei.in> * update Signed-off-by: Innei <i@innei.in> --------- Signed-off-by: Innei <i@innei.in>
This commit is contained in:
parent
01e087bc71
commit
62aae0d244
|
|
@ -146,6 +146,7 @@
|
|||
"electron-vite": "^2.3.0",
|
||||
"eslint": "^9.7.0",
|
||||
"eslint-config-hyoban": "3.0.0-beta.27",
|
||||
"fake-indexeddb": "6.0.0",
|
||||
"hono": "4.4.7",
|
||||
"lint-staged": "15.2.7",
|
||||
"postcss": "8.4.39",
|
||||
|
|
@ -157,6 +158,7 @@
|
|||
"tailwindcss": "3.4.6",
|
||||
"typescript": "^5.5.3",
|
||||
"vite": "^5.3.4",
|
||||
"vite-tsconfig-paths": "4.3.2",
|
||||
"vitest": "2.0.3"
|
||||
},
|
||||
"pnpm": {
|
||||
|
|
|
|||
|
|
@ -362,6 +362,9 @@ importers:
|
|||
eslint-config-hyoban:
|
||||
specifier: 3.0.0-beta.27
|
||||
version: 3.0.0-beta.27(eslint@9.7.0)(tailwindcss@3.4.6)(typescript@5.5.3)
|
||||
fake-indexeddb:
|
||||
specifier: 6.0.0
|
||||
version: 6.0.0
|
||||
hono:
|
||||
specifier: 4.4.7
|
||||
version: 4.4.7(patch_hash=ycbk46disqruhfjducp47b5fl4)
|
||||
|
|
@ -395,6 +398,9 @@ importers:
|
|||
vite:
|
||||
specifier: ^5.3.4
|
||||
version: 5.3.4(@types/node@20.14.11)
|
||||
vite-tsconfig-paths:
|
||||
specifier: 4.3.2
|
||||
version: 4.3.2(typescript@5.5.3)(vite@5.3.4(@types/node@20.14.11))
|
||||
vitest:
|
||||
specifier: 2.0.3
|
||||
version: 2.0.3(@types/node@20.14.11)
|
||||
|
|
@ -3979,6 +3985,10 @@ packages:
|
|||
resolution: {integrity: sha512-Wrk35e8ydCKDj/ArClo1VrPVmN8zph5V4AtHwIuHhvMXsKf73UT3BOD+azBIW+3wOJ4FhEH7zyaJCFvChjYvMA==}
|
||||
engines: {'0': node >=0.6.0}
|
||||
|
||||
fake-indexeddb@6.0.0:
|
||||
resolution: {integrity: sha512-YEboHE5VfopUclOck7LncgIqskAqnv4q0EWbYCaxKKjAvO93c+TJIaBuGy8CBFdbg9nKdpN3AuPRwVBJ4k7NrQ==}
|
||||
engines: {node: '>=18'}
|
||||
|
||||
fast-deep-equal@3.1.3:
|
||||
resolution: {integrity: sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==}
|
||||
|
||||
|
|
@ -4295,6 +4305,9 @@ packages:
|
|||
resolution: {integrity: sha512-jhIXaOzy1sb8IyocaruWSn1TjmnBVs8Ayhcy83rmxNJ8q2uWKCAj3CnJY+KpGSXCueAPc0i05kVvVKtP1t9S3g==}
|
||||
engines: {node: '>=10'}
|
||||
|
||||
globrex@0.1.2:
|
||||
resolution: {integrity: sha512-uHJgbwAMwNFf5mLst7IWLNg14x1CkeqglJb/K3doi4dw6q2IvAAmM/Y81kevy83wP+Sst+nutFTYOGg3d1lsxg==}
|
||||
|
||||
gopd@1.0.1:
|
||||
resolution: {integrity: sha512-d65bNlIadxvpb/A2abVdlqKqV563juRnZ1Wtk6s1sIR8uNsXR70xqIzVqxVf1eTqDunwT2MkczEeaezCKTZhwA==}
|
||||
|
||||
|
|
@ -6387,6 +6400,16 @@ packages:
|
|||
ts-interface-checker@0.1.13:
|
||||
resolution: {integrity: sha512-Y/arvbn+rrz3JCKl9C4kVNfTfSm2/mEp5FSz5EsZSANGPSlQrpRI5M4PKF+mJnE52jOO90PnPSc3Ur3bTQw0gA==}
|
||||
|
||||
tsconfck@3.1.1:
|
||||
resolution: {integrity: sha512-00eoI6WY57SvZEVjm13stEVE90VkEdJAFGgpFLTsZbJyW/LwFQ7uQxJHWpZ2hzSWgCPKc9AnBnNP+0X7o3hAmQ==}
|
||||
engines: {node: ^18 || >=20}
|
||||
hasBin: true
|
||||
peerDependencies:
|
||||
typescript: ^5.0.0
|
||||
peerDependenciesMeta:
|
||||
typescript:
|
||||
optional: true
|
||||
|
||||
tslib@2.6.3:
|
||||
resolution: {integrity: sha512-xNvxJEOUiWPGhUuUdQgAJPKOOJfGnIyKySOc09XkKsgdUV/3E2zvwZYdejjmRgPCgcym1juLH3226yA7sEFJKQ==}
|
||||
|
||||
|
|
@ -6581,6 +6604,14 @@ packages:
|
|||
engines: {node: ^18.0.0 || >=20.0.0}
|
||||
hasBin: true
|
||||
|
||||
vite-tsconfig-paths@4.3.2:
|
||||
resolution: {integrity: sha512-0Vd/a6po6Q+86rPlntHye7F31zA2URZMbH8M3saAZ/xR9QoGN/L21bxEGfXdWmFdNkqPpRdxFT7nmNe12e9/uA==}
|
||||
peerDependencies:
|
||||
vite: '*'
|
||||
peerDependenciesMeta:
|
||||
vite:
|
||||
optional: true
|
||||
|
||||
vite@5.3.4:
|
||||
resolution: {integrity: sha512-Cw+7zL3ZG9/NZBB8C+8QbQZmR54GwqIz+WMI4b3JgdYJvX+ny9AjJXqkGQlDXSXRP9rP0B4tbciRMOVEKulVOA==}
|
||||
engines: {node: ^18.0.0 || >=20.0.0}
|
||||
|
|
@ -11193,6 +11224,8 @@ snapshots:
|
|||
extsprintf@1.4.1:
|
||||
optional: true
|
||||
|
||||
fake-indexeddb@6.0.0: {}
|
||||
|
||||
fast-deep-equal@3.1.3: {}
|
||||
|
||||
fast-equals@5.0.1: {}
|
||||
|
|
@ -11562,6 +11595,8 @@ snapshots:
|
|||
merge2: 1.4.1
|
||||
slash: 3.0.0
|
||||
|
||||
globrex@0.1.2: {}
|
||||
|
||||
gopd@1.0.1:
|
||||
dependencies:
|
||||
get-intrinsic: 1.2.4
|
||||
|
|
@ -13889,6 +13924,10 @@ snapshots:
|
|||
|
||||
ts-interface-checker@0.1.13: {}
|
||||
|
||||
tsconfck@3.1.1(typescript@5.5.3):
|
||||
optionalDependencies:
|
||||
typescript: 5.5.3
|
||||
|
||||
tslib@2.6.3: {}
|
||||
|
||||
type-check@0.4.0:
|
||||
|
|
@ -14098,6 +14137,17 @@ snapshots:
|
|||
- supports-color
|
||||
- terser
|
||||
|
||||
vite-tsconfig-paths@4.3.2(typescript@5.5.3)(vite@5.3.4(@types/node@20.14.11)):
|
||||
dependencies:
|
||||
debug: 4.3.5
|
||||
globrex: 0.1.2
|
||||
tsconfck: 3.1.1(typescript@5.5.3)
|
||||
optionalDependencies:
|
||||
vite: 5.3.4(@types/node@20.14.11)
|
||||
transitivePeerDependencies:
|
||||
- supports-color
|
||||
- typescript
|
||||
|
||||
vite@5.3.4(@types/node@20.14.11):
|
||||
dependencies:
|
||||
esbuild: 0.21.5
|
||||
|
|
|
|||
|
|
@ -0,0 +1 @@
|
|||
import "fake-indexeddb/auto"
|
||||
|
|
@ -2,18 +2,25 @@ import type { Transaction } from "dexie"
|
|||
import Dexie from "dexie"
|
||||
|
||||
import { LOCAL_DB_NAME } from "./constants"
|
||||
import { dbSchemaV1, dbSchemaV2, dbSchemaV3 } from "./db_schema"
|
||||
import type { DB_Base } from "./schemas/base"
|
||||
import type { DB_FeedId } from "./schemas/feed"
|
||||
import type { DBModel } from "./types"
|
||||
import {
|
||||
dbSchemaV1,
|
||||
dbSchemaV2,
|
||||
dbSchemaV3,
|
||||
dbSchemaV4,
|
||||
dbSchemaV5,
|
||||
} from "./db_schema"
|
||||
import type { DB_Cleaner } from "./schemas/cleaner"
|
||||
import type { DB_Entry, DB_EntryRelated } from "./schemas/entry"
|
||||
import type { DB_Feed, DB_FeedUnread } from "./schemas/feed"
|
||||
import type { DB_Subscription } from "./schemas/subscription"
|
||||
|
||||
export interface LocalDBSchemaMap {
|
||||
entries: DB_Base
|
||||
feeds: DB_Base
|
||||
subscriptions: DB_Base
|
||||
entryRelated: DB_Base
|
||||
feedEntries: DB_FeedId
|
||||
feedUnreads: DB_FeedId
|
||||
entries: DB_Entry
|
||||
feeds: DB_Feed
|
||||
subscriptions: DB_Subscription
|
||||
entryRelated: DB_EntryRelated
|
||||
feedUnreads: DB_FeedUnread
|
||||
cleaner: DB_Cleaner
|
||||
}
|
||||
|
||||
// Define a local DB
|
||||
|
|
@ -22,22 +29,23 @@ export class BrowserDB extends Dexie {
|
|||
public feeds: BrowserDBTable<"feeds">
|
||||
public subscriptions: BrowserDBTable<"subscriptions">
|
||||
public entryRelated: BrowserDBTable<"entryRelated">
|
||||
public feedEntries: BrowserDBTable<"feedEntries">
|
||||
public feedUnreads: BrowserDBTable<"feedUnreads">
|
||||
public cleaner: BrowserDBTable<"cleaner">
|
||||
|
||||
constructor() {
|
||||
super(LOCAL_DB_NAME)
|
||||
this.version(1).stores(dbSchemaV1)
|
||||
this.version(2).stores(dbSchemaV2)
|
||||
.upgrade(this.upgradeToV2)
|
||||
this.version(2).stores(dbSchemaV2).upgrade(this.upgradeToV2)
|
||||
this.version(3).stores(dbSchemaV3)
|
||||
this.version(4).stores(dbSchemaV4)
|
||||
this.version(5).stores(dbSchemaV5)
|
||||
|
||||
this.entries = this.table("entries")
|
||||
this.feeds = this.table("feeds")
|
||||
this.subscriptions = this.table("subscriptions")
|
||||
this.entryRelated = this.table("entryRelated")
|
||||
this.feedEntries = this.table("feedEntries")
|
||||
this.feedUnreads = this.table("feedUnreads")
|
||||
this.cleaner = this.table("cleaner")
|
||||
}
|
||||
|
||||
async upgradeToV2(trans: Transaction) {
|
||||
|
|
@ -58,7 +66,7 @@ export const browserDB = new BrowserDB()
|
|||
export type BrowserDBSchema = {
|
||||
[t in keyof LocalDBSchemaMap]: {
|
||||
model: LocalDBSchemaMap[t]
|
||||
table: Dexie.Table<DBModel<LocalDBSchemaMap[t]>, string>
|
||||
table: Dexie.Table<LocalDBSchemaMap[t], string>
|
||||
};
|
||||
}
|
||||
type BrowserDBTable<T extends keyof LocalDBSchemaMap> =
|
||||
|
|
|
|||
|
|
@ -3,7 +3,6 @@ export const dbSchemaV1 = {
|
|||
feeds: "&id",
|
||||
subscriptions: "&id",
|
||||
entryRelated: "&id",
|
||||
feedEntries: "&feedId",
|
||||
feedUnreads: "&id",
|
||||
}
|
||||
|
||||
|
|
@ -13,6 +12,19 @@ export const dbSchemaV2 = {
|
|||
}
|
||||
|
||||
export const dbSchemaV3 = {
|
||||
...dbSchemaV1,
|
||||
...dbSchemaV2,
|
||||
feedEntries: null,
|
||||
|
||||
subscriptions: "&id, userId, &feedId",
|
||||
}
|
||||
|
||||
export const dbSchemaV4 = {
|
||||
...dbSchemaV3,
|
||||
entries: "&id, feedId",
|
||||
subscriptions: "&id, userId, feedId",
|
||||
}
|
||||
|
||||
export const dbSchemaV5 = {
|
||||
...dbSchemaV4,
|
||||
cleaner: "&refId, visitedAt",
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,7 +1,6 @@
|
|||
import { browserDB } from "./db"
|
||||
|
||||
export * from "./db"
|
||||
export * from "./models"
|
||||
export * from "./schemas"
|
||||
|
||||
export const DB_NOT_READY_OR_DISABLED = "Database is not ready or disabled"
|
||||
|
|
|
|||
|
|
@ -1,31 +0,0 @@
|
|||
/**
|
||||
* @see https://github.com/lobehub/lobe-chat/blob/adebf0a92167faad48581b0b0780cf8faeba362f/src/database/client/core/model.ts
|
||||
*/
|
||||
|
||||
import type Dexie from "dexie"
|
||||
import type { ZodObject } from "zod"
|
||||
|
||||
import type { BrowserDB, BrowserDBSchema } from "./db"
|
||||
import { browserDB } from "./db"
|
||||
|
||||
export class BaseModel<
|
||||
N extends keyof BrowserDBSchema = any,
|
||||
// T extends { id: string } = any,
|
||||
// T = BrowserDBSchema[N]["table"],
|
||||
> {
|
||||
protected readonly db: BrowserDB
|
||||
// used to data validation, but use now
|
||||
|
||||
private readonly schema: ZodObject<any>
|
||||
private readonly _tableName: keyof BrowserDBSchema
|
||||
|
||||
constructor(table: N, schema: ZodObject<any>, db = browserDB) {
|
||||
this.db = db
|
||||
this.schema = schema
|
||||
this._tableName = table
|
||||
}
|
||||
|
||||
get table() {
|
||||
return this.db[this._tableName] as Dexie.Table
|
||||
}
|
||||
}
|
||||
|
|
@ -1,10 +0,0 @@
|
|||
import { BaseModel } from "../model"
|
||||
import { DB_BaseSchema } from "../schemas"
|
||||
|
||||
class ModelStatic extends BaseModel<"entryRelated"> {
|
||||
constructor() {
|
||||
super("entryRelated", DB_BaseSchema)
|
||||
}
|
||||
}
|
||||
|
||||
export const entryRelatedModel = new ModelStatic()
|
||||
|
|
@ -1,10 +0,0 @@
|
|||
import { BaseModel } from "../model"
|
||||
import { DB_BaseSchema } from "../schemas"
|
||||
|
||||
class EntryModelStatic extends BaseModel<"entries"> {
|
||||
constructor() {
|
||||
super("entries", DB_BaseSchema)
|
||||
}
|
||||
}
|
||||
|
||||
export const entryModel = new EntryModelStatic()
|
||||
|
|
@ -1,10 +0,0 @@
|
|||
import { BaseModel } from "../model"
|
||||
import { DB_BaseSchema } from "../schemas"
|
||||
|
||||
class ModelStatic extends BaseModel<"feedEntries"> {
|
||||
constructor() {
|
||||
super("feedEntries", DB_BaseSchema)
|
||||
}
|
||||
}
|
||||
|
||||
export const feedEntriesModel = new ModelStatic()
|
||||
|
|
@ -1,10 +0,0 @@
|
|||
import { BaseModel } from "../model"
|
||||
import { DB_BaseSchema } from "../schemas"
|
||||
|
||||
class ModelStatic extends BaseModel<"feeds"> {
|
||||
constructor() {
|
||||
super("feeds", DB_BaseSchema)
|
||||
}
|
||||
}
|
||||
|
||||
export const feedModel = new ModelStatic()
|
||||
|
|
@ -1,5 +0,0 @@
|
|||
export * from "./entry"
|
||||
export * from "./entry-related"
|
||||
export * from "./feed"
|
||||
export * from "./feed-entry"
|
||||
export * from "./subscription"
|
||||
|
|
@ -1,10 +0,0 @@
|
|||
import { BaseModel } from "../model"
|
||||
import { DB_BaseSchema } from "../schemas"
|
||||
|
||||
class ModelStatic extends BaseModel<"subscriptions"> {
|
||||
constructor() {
|
||||
super("subscriptions", DB_BaseSchema)
|
||||
}
|
||||
}
|
||||
|
||||
export const subscriptionModel = new ModelStatic()
|
||||
|
|
@ -1,10 +0,0 @@
|
|||
import { BaseModel } from "../model"
|
||||
import { DB_BaseSchema } from "../schemas"
|
||||
|
||||
class ModelStatic extends BaseModel<"feedUnreads"> {
|
||||
constructor() {
|
||||
super("feedUnreads", DB_BaseSchema)
|
||||
}
|
||||
}
|
||||
|
||||
export const feedUnreadModel = new ModelStatic()
|
||||
|
|
@ -1,3 +0,0 @@
|
|||
import type { users } from "@renderer/hono"
|
||||
|
||||
export type UserModel = Omit<typeof users.$inferSelect, "emailVerified">
|
||||
|
|
@ -0,0 +1,5 @@
|
|||
export type DB_Cleaner = {
|
||||
refId: string
|
||||
visitedAt: number
|
||||
type: "entry" | "feed"
|
||||
}
|
||||
|
|
@ -0,0 +1,8 @@
|
|||
import type { EntryModel } from "@renderer/models"
|
||||
|
||||
export type DB_Entry = EntryModel & { feedId: string }
|
||||
|
||||
export type DB_EntryRelated = {
|
||||
id: string
|
||||
data: any
|
||||
}
|
||||
|
|
@ -1,7 +1,8 @@
|
|||
import { z } from "zod"
|
||||
import type { FeedModel } from "@renderer/models"
|
||||
|
||||
export const DB_FeedIdSchema = z.object({
|
||||
feedId: z.string(),
|
||||
})
|
||||
export type DB_FeedUnread = {
|
||||
id: string
|
||||
count: number
|
||||
}
|
||||
|
||||
export type DB_FeedId = z.infer<typeof DB_FeedIdSchema>
|
||||
export type DB_Feed = FeedModel & { id: string }
|
||||
|
|
|
|||
|
|
@ -0,0 +1,5 @@
|
|||
import type { SubscriptionFlatModel } from "@renderer/store/subscription"
|
||||
|
||||
export type DB_Subscription = SubscriptionFlatModel & {
|
||||
id: string
|
||||
}
|
||||
|
|
@ -1,5 +0,0 @@
|
|||
export type DBModel<T> = T & {
|
||||
createdAt: number
|
||||
id: string
|
||||
updatedAt: number
|
||||
}
|
||||
|
|
@ -71,7 +71,7 @@ async function hydrateEntry() {
|
|||
|
||||
const storeValue = [] as FlatEntryModel[]
|
||||
for (const entry of entries) {
|
||||
const entryRelatedFeedId = feedEntries[entry.id]
|
||||
const entryRelatedFeedId = entry.feedId || feedEntries[entry.id]
|
||||
if (!entryRelatedFeedId) {
|
||||
logHydrateError(`Entry ${entry.id} has no related feed id`)
|
||||
continue
|
||||
|
|
|
|||
|
|
@ -2,6 +2,7 @@ import { env } from "@env"
|
|||
import { authConfigManager } from "@hono/auth-js/react"
|
||||
import { repository } from "@pkg"
|
||||
import { browserDB } from "@renderer/database"
|
||||
import { CleanerService } from "@renderer/services/cleaner"
|
||||
import { registerGlobalContext } from "@shared/bridge"
|
||||
import { enableMapSet } from "immer"
|
||||
import { toast } from "sonner"
|
||||
|
|
@ -62,6 +63,7 @@ export const initializeApp = async () => {
|
|||
// Initialize the database
|
||||
if (enabledDataPersist) {
|
||||
dataHydratedTime = await hydrateDatabaseToStore()
|
||||
CleanerService.cleanOutdatedData()
|
||||
}
|
||||
|
||||
// Initialize the auth config
|
||||
|
|
|
|||
|
|
@ -1,5 +1,8 @@
|
|||
import type { users } from "@renderer/hono"
|
||||
import type { apiClient } from "@renderer/lib/api-fetch"
|
||||
|
||||
export type UserModel = Omit<typeof users.$inferSelect, "emailVerified">
|
||||
|
||||
export type ExtractBizResponse<T extends (...args: any[]) => any> = Exclude<
|
||||
Awaited<ReturnType<T>>,
|
||||
undefined
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
import { useSetMe } from "@renderer/atoms/user"
|
||||
import { useSession } from "@renderer/queries/auth"
|
||||
import { CleanerService } from "@renderer/services/cleaner"
|
||||
import { useEffect } from "react"
|
||||
|
||||
export const UserProvider = () => {
|
||||
|
|
@ -13,6 +14,8 @@ export const UserProvider = () => {
|
|||
name: session.user.name,
|
||||
handle: session.user.handle,
|
||||
})
|
||||
|
||||
CleanerService.cleanRemainingData(session.user.id)
|
||||
}, [session?.user, setUser])
|
||||
|
||||
return null
|
||||
|
|
|
|||
|
|
@ -0,0 +1,85 @@
|
|||
// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html
|
||||
|
||||
exports[`test db cleaner > data should be set up correctly 1`] = `
|
||||
[
|
||||
{
|
||||
"id": "feed-id-1",
|
||||
},
|
||||
{
|
||||
"id": "feed-id-2",
|
||||
},
|
||||
{
|
||||
"id": "feed-id-3",
|
||||
},
|
||||
{
|
||||
"id": "feed-id-4",
|
||||
},
|
||||
{
|
||||
"id": "feed-id-5",
|
||||
},
|
||||
]
|
||||
`;
|
||||
|
||||
exports[`test db cleaner > data should be set up correctly 2`] = `
|
||||
[
|
||||
{
|
||||
"feedId": "feed-id-1",
|
||||
"id": "entry-id-1",
|
||||
},
|
||||
{
|
||||
"feedId": "feed-id-2",
|
||||
"id": "entry-id-2",
|
||||
},
|
||||
{
|
||||
"feedId": "feed-id-3",
|
||||
"id": "entry-id-3",
|
||||
},
|
||||
{
|
||||
"feedId": "feed-id-4",
|
||||
"id": "entry-id-4",
|
||||
},
|
||||
{
|
||||
"feedId": "feed-id-5",
|
||||
"id": "entry-id-5",
|
||||
},
|
||||
{
|
||||
"feedId": "feed-id-1",
|
||||
"id": "entry-id-6",
|
||||
},
|
||||
]
|
||||
`;
|
||||
|
||||
exports[`test db cleaner > data should be set up correctly 3`] = `
|
||||
[
|
||||
{
|
||||
"feedId": "feed-id-1",
|
||||
"id": "test-user-id-1/feed-id-1",
|
||||
"userId": "test-user-id-1",
|
||||
},
|
||||
{
|
||||
"feedId": "feed-id-3",
|
||||
"id": "test-user-id-1/feed-id-3",
|
||||
"userId": "test-user-id-1",
|
||||
},
|
||||
{
|
||||
"feedId": "feed-id-4",
|
||||
"id": "test-user-id-2/feed-id-4",
|
||||
"userId": "test-user-id-2",
|
||||
},
|
||||
{
|
||||
"feedId": "feed-id-5",
|
||||
"id": "test-user-id-3/feed-id-5",
|
||||
"userId": "test-user-id-3",
|
||||
},
|
||||
{
|
||||
"feedId": "feed-id-1",
|
||||
"id": "test-user-id/feed-id-1",
|
||||
"userId": "test-user-id",
|
||||
},
|
||||
{
|
||||
"feedId": "feed-id-2",
|
||||
"id": "test-user-id/feed-id-2",
|
||||
"userId": "test-user-id",
|
||||
},
|
||||
]
|
||||
`;
|
||||
|
|
@ -4,14 +4,6 @@ import type { UpdateSpec } from "dexie"
|
|||
export abstract class BaseService<T extends { id: string }> {
|
||||
constructor(public readonly table: Dexie.Table<T, string>) {}
|
||||
|
||||
async create(data: T) {
|
||||
return this.table.add(data)
|
||||
}
|
||||
|
||||
async insertMany(data: T[]) {
|
||||
return this.table.bulkAdd(data)
|
||||
}
|
||||
|
||||
async upsert(data: T): Promise<unknown> {
|
||||
return this.table.put(data)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,157 @@
|
|||
// @ts-nocheck
|
||||
import { browserDB } from "@renderer/database"
|
||||
import type { FeedModel } from "@renderer/hono"
|
||||
import type { EntryModel } from "@renderer/models"
|
||||
import type { SubscriptionFlatModel } from "@renderer/store/subscription"
|
||||
import { beforeAll, describe, expect, test } from "vitest"
|
||||
|
||||
import { CleanerService } from "./cleaner"
|
||||
import { EntryService } from "./entry"
|
||||
import { FeedService } from "./feed"
|
||||
import { SubscriptionService } from "./subscription"
|
||||
|
||||
const currentUserID = "test-user-id"
|
||||
const otherUserIDs = ["test-user-id-1", "test-user-id-2", "test-user-id-3"]
|
||||
|
||||
const subscriptions: SubscriptionFlatModel[] = [
|
||||
{
|
||||
feedId: "feed-id-1",
|
||||
userId: currentUserID,
|
||||
},
|
||||
{
|
||||
feedId: "feed-id-2",
|
||||
userId: currentUserID,
|
||||
},
|
||||
{
|
||||
feedId: "feed-id-3",
|
||||
userId: otherUserIDs[0],
|
||||
},
|
||||
{
|
||||
feedId: "feed-id-4",
|
||||
userId: otherUserIDs[1],
|
||||
},
|
||||
{
|
||||
feedId: "feed-id-5",
|
||||
userId: otherUserIDs[2],
|
||||
},
|
||||
// ====
|
||||
{
|
||||
feedId: "feed-id-1",
|
||||
userId: otherUserIDs[0],
|
||||
},
|
||||
]
|
||||
|
||||
const feeds: FeedModel[] = [
|
||||
{
|
||||
id: "feed-id-1",
|
||||
},
|
||||
{
|
||||
id: "feed-id-2",
|
||||
},
|
||||
{
|
||||
id: "feed-id-3",
|
||||
},
|
||||
{
|
||||
id: "feed-id-4",
|
||||
},
|
||||
{
|
||||
id: "feed-id-5",
|
||||
},
|
||||
]
|
||||
|
||||
const entries: EntryModel[] = [
|
||||
{
|
||||
id: "entry-id-1",
|
||||
},
|
||||
{
|
||||
id: "entry-id-2",
|
||||
},
|
||||
{
|
||||
id: "entry-id-3",
|
||||
},
|
||||
{
|
||||
id: "entry-id-4",
|
||||
},
|
||||
{
|
||||
id: "entry-id-5",
|
||||
},
|
||||
{
|
||||
id: "entry-id-6",
|
||||
},
|
||||
]
|
||||
|
||||
const entryFeedIdMap = {
|
||||
"entry-id-1": "feed-id-1",
|
||||
"entry-id-2": "feed-id-2",
|
||||
"entry-id-3": "feed-id-3",
|
||||
"entry-id-4": "feed-id-4",
|
||||
"entry-id-5": "feed-id-5",
|
||||
"entry-id-6": "feed-id-1",
|
||||
}
|
||||
describe("test db cleaner", () => {
|
||||
beforeAll(async () => {
|
||||
await browserDB.delete()
|
||||
})
|
||||
beforeAll(async () => {
|
||||
await browserDB.open()
|
||||
await SubscriptionService.upsertMany(subscriptions)
|
||||
await FeedService.upsertMany(feeds)
|
||||
await EntryService.upsertMany(entries, entryFeedIdMap)
|
||||
})
|
||||
|
||||
test("data should be set up correctly", async () => {
|
||||
const feeds = await FeedService.findAll()
|
||||
const entries = await EntryService.findAll()
|
||||
const subscriptions = await SubscriptionService.findAll()
|
||||
expect(feeds).toMatchSnapshot()
|
||||
expect(entries).toMatchSnapshot()
|
||||
expect(subscriptions).toMatchSnapshot()
|
||||
})
|
||||
test("should clean remaining data", async () => {
|
||||
await CleanerService.cleanRemainingData(currentUserID)
|
||||
|
||||
const feeds = await FeedService.findAll()
|
||||
expect(feeds).toMatchInlineSnapshot(`
|
||||
[
|
||||
{
|
||||
"id": "feed-id-1",
|
||||
},
|
||||
{
|
||||
"id": "feed-id-2",
|
||||
},
|
||||
]
|
||||
`)
|
||||
const subscripions = await SubscriptionService.findAll()
|
||||
expect(subscripions).toMatchInlineSnapshot(`
|
||||
[
|
||||
{
|
||||
"feedId": "feed-id-1",
|
||||
"id": "test-user-id/feed-id-1",
|
||||
"userId": "test-user-id",
|
||||
},
|
||||
{
|
||||
"feedId": "feed-id-2",
|
||||
"id": "test-user-id/feed-id-2",
|
||||
"userId": "test-user-id",
|
||||
},
|
||||
]
|
||||
`)
|
||||
const entries = await EntryService.findAll()
|
||||
expect(entries).toMatchInlineSnapshot(`
|
||||
[
|
||||
{
|
||||
"feedId": "feed-id-1",
|
||||
"id": "entry-id-1",
|
||||
},
|
||||
{
|
||||
"feedId": "feed-id-2",
|
||||
"id": "entry-id-2",
|
||||
},
|
||||
{
|
||||
"feedId": "feed-id-1",
|
||||
"id": "entry-id-6",
|
||||
},
|
||||
]
|
||||
`)
|
||||
})
|
||||
})
|
||||
|
|
@ -0,0 +1,90 @@
|
|||
import { browserDB } from "@renderer/database"
|
||||
import { appLog } from "@renderer/lib/log"
|
||||
|
||||
import { EntryService } from "./entry"
|
||||
import { FeedService } from "./feed"
|
||||
import { FeedUnreadService } from "./feed-unread"
|
||||
import { SubscriptionService } from "./subscription"
|
||||
|
||||
const cleanerModel = browserDB.cleaner
|
||||
class CleanerServiceStatic {
|
||||
// Clean other user subscriptions, should call this after login
|
||||
async cleanRemainingData(currentUserId: string) {
|
||||
const dbUserIds = await SubscriptionService.getUserIds()
|
||||
|
||||
const otherUserIds = dbUserIds.filter((id) => id !== currentUserId)
|
||||
const remainingSubscriptions =
|
||||
await SubscriptionService.getUserSubscriptions(otherUserIds)
|
||||
const currentSubscription = await SubscriptionService.getUserSubscriptions([
|
||||
currentUserId,
|
||||
])
|
||||
const currentSubscriptionFeedsSet = new Set(
|
||||
currentSubscription.map((s) => s.feedId),
|
||||
)
|
||||
// Finds a subscripion that does not exist for the current user, but a feedId that exists in the db
|
||||
|
||||
const toRemoveSubscription = remainingSubscriptions.filter(
|
||||
(s) => !currentSubscriptionFeedsSet.has(s.feedId),
|
||||
)
|
||||
|
||||
const toRemoveFeedIds = toRemoveSubscription.map((s) => s.feedId)
|
||||
|
||||
await Promise.allSettled([
|
||||
otherUserIds.map((id) => SubscriptionService.removeSubscription(id)),
|
||||
FeedService.bulkDelete(toRemoveFeedIds),
|
||||
FeedUnreadService.bulkDelete(toRemoveFeedIds),
|
||||
EntryService.deleteEntriesByFeedIds(toRemoveFeedIds),
|
||||
])
|
||||
}
|
||||
|
||||
/**
|
||||
* Mark the which data recently used
|
||||
*/
|
||||
renew(list: { type: "feed" | "entry", id: string }[]) {
|
||||
const now = Date.now()
|
||||
return cleanerModel.bulkPut(
|
||||
list.map((item) => ({
|
||||
refId: item.id,
|
||||
visitedAt: now,
|
||||
type: item.type,
|
||||
})),
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove the data that not used for a long time
|
||||
*/
|
||||
async cleanOutdatedData() {
|
||||
const now = Date.now()
|
||||
const expiredTime = now - 1000 * 60 * 60 * 24 * 30 // 30 days
|
||||
const data = await cleanerModel
|
||||
.where("visitedAt")
|
||||
.below(expiredTime)
|
||||
.toArray()
|
||||
|
||||
if (data.length === 0) { return }
|
||||
const deleteEntries = [] as string[]
|
||||
const deleteFeeds = [] as string[]
|
||||
for (const item of data) {
|
||||
switch (item.type) {
|
||||
case "feed": {
|
||||
deleteFeeds.push(item.refId)
|
||||
break
|
||||
}
|
||||
case "entry": {
|
||||
deleteEntries.push(item.refId)
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
appLog("Clean outdated data...", "feeds:", deleteFeeds.length, "entries:", deleteEntries.length)
|
||||
await Promise.allSettled([
|
||||
FeedService.bulkDelete(deleteFeeds),
|
||||
EntryService.deleteEntries(deleteEntries),
|
||||
EntryService.deleteEntriesByFeedIds(deleteFeeds),
|
||||
cleanerModel.bulkDelete(data.map((d) => d.refId)),
|
||||
])
|
||||
}
|
||||
}
|
||||
export const CleanerService = new CleanerServiceStatic()
|
||||
|
|
@ -1,7 +1,8 @@
|
|||
import { entryRelatedModel } from "@renderer/database"
|
||||
import { browserDB } from "@renderer/database"
|
||||
|
||||
export enum EntryRelatedKey {
|
||||
READ = "READ",
|
||||
/** @deprecated */
|
||||
FEED_ID = "FEED_ID",
|
||||
COLLECTION = "COLLECTION",
|
||||
}
|
||||
|
|
@ -17,13 +18,14 @@ const taskQueue = new Map<EntryRelatedKey, Promise<any>>(
|
|||
type IdToIdRecord = Record<string, string>
|
||||
type IdToBooleanRecord = Record<string, boolean>
|
||||
type IdToAnyObjectRecord = Record<string, Record<string, any>>
|
||||
const entryRelatedModel = browserDB.entryRelated
|
||||
class ServiceStatic {
|
||||
async findAll(type: EntryRelatedKey.FEED_ID): Promise<IdToIdRecord>
|
||||
async findAll(type: EntryRelatedKey.READ): Promise<IdToBooleanRecord>
|
||||
async findAll(type: EntryRelatedKey.COLLECTION): Promise<IdToAnyObjectRecord>
|
||||
|
||||
async findAll(type: EntryRelatedKey): Promise<Record<string, any>> {
|
||||
const data = await entryRelatedModel.table.get(type)
|
||||
const data = await entryRelatedModel.get(type)
|
||||
return data ? data.data : {}
|
||||
}
|
||||
|
||||
|
|
@ -50,7 +52,7 @@ class ServiceStatic {
|
|||
const task = getPreviousTask.finally(async () => {
|
||||
const oldData = await this.findAll(type)
|
||||
|
||||
entryRelatedModel.table.put({
|
||||
entryRelatedModel.put({
|
||||
data: { ...oldData, ...data },
|
||||
id: type,
|
||||
})
|
||||
|
|
@ -64,14 +66,14 @@ class ServiceStatic {
|
|||
const oldData = await this.findAll(type as any)
|
||||
delete oldData[key]
|
||||
|
||||
return entryRelatedModel.table.put({
|
||||
return entryRelatedModel.put({
|
||||
data: oldData,
|
||||
id: type,
|
||||
})
|
||||
}
|
||||
|
||||
async clear() {
|
||||
return entryRelatedModel.table.clear()
|
||||
return entryRelatedModel.clear()
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1,7 +1,8 @@
|
|||
import { entryModel } from "@renderer/database/models"
|
||||
import { browserDB } from "@renderer/database"
|
||||
import type { EntryModel } from "@renderer/models/types"
|
||||
|
||||
import { BaseService } from "./base"
|
||||
import { CleanerService } from "./cleaner"
|
||||
import { EntryRelatedKey, EntryRelatedService } from "./entry-related"
|
||||
|
||||
type EntryCollection = {
|
||||
|
|
@ -9,13 +10,59 @@ type EntryCollection = {
|
|||
}
|
||||
class EntryServiceStatic extends BaseService<EntryModel> {
|
||||
constructor() {
|
||||
super(entryModel.table)
|
||||
super(browserDB.entries)
|
||||
}
|
||||
|
||||
// @ts-expect-error
|
||||
override async upsertMany(
|
||||
data: EntryModel[],
|
||||
entryFeedMap: Record<string, string>,
|
||||
) {
|
||||
const renewList = [] as { type: "entry", id: string }[]
|
||||
const nextData = [] as (EntryModel & { feedId: string })[]
|
||||
|
||||
for (const item of data) {
|
||||
const feedId = entryFeedMap[item.id]
|
||||
if (!feedId) {
|
||||
console.error("EntryService.upsertMany: feedId not found", item)
|
||||
continue
|
||||
}
|
||||
renewList.push({ type: "entry", id: item.id })
|
||||
nextData.push({
|
||||
...item,
|
||||
feedId,
|
||||
})
|
||||
}
|
||||
|
||||
CleanerService.renew(renewList)
|
||||
|
||||
return super.upsertMany(nextData)
|
||||
}
|
||||
|
||||
// @ts-ignore
|
||||
override async upsert(feedId: string, data: EntryModel): Promise<unknown> {
|
||||
CleanerService.renew([
|
||||
{
|
||||
type: "entry",
|
||||
id: data.id,
|
||||
},
|
||||
])
|
||||
return super.upsert({
|
||||
...data,
|
||||
// @ts-expect-error
|
||||
feedId,
|
||||
})
|
||||
}
|
||||
|
||||
override async findAll() {
|
||||
return super.findAll() as Promise<(EntryModel & { feedId: string })[]>
|
||||
}
|
||||
|
||||
bulkStoreReadStatus(record: Record<string, boolean>) {
|
||||
return EntryRelatedService.upsert(EntryRelatedKey.READ, record)
|
||||
}
|
||||
|
||||
/** @deprecated */
|
||||
bulkStoreFeedId(record: Record<string, string>) {
|
||||
return EntryRelatedService.upsert(EntryRelatedKey.FEED_ID, record)
|
||||
}
|
||||
|
|
@ -29,7 +76,11 @@ class EntryServiceStatic extends BaseService<EntryModel> {
|
|||
}
|
||||
|
||||
async deleteEntries(entryIds: string[]) {
|
||||
await entryModel.table.bulkDelete(entryIds)
|
||||
await this.table.bulkDelete(entryIds)
|
||||
}
|
||||
|
||||
async deleteEntriesByFeedIds(feedIds: string[]) {
|
||||
await this.table.where("feedId").anyOf(feedIds).delete()
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1,20 +0,0 @@
|
|||
import { feedEntriesModel } from "@renderer/database"
|
||||
|
||||
class ServiceStatic {
|
||||
constructor() {}
|
||||
|
||||
updateFeed(feedId: string, entryIds: string[]) {
|
||||
return feedEntriesModel.table.put({
|
||||
feedId,
|
||||
entryIds,
|
||||
})
|
||||
}
|
||||
|
||||
getAll() {
|
||||
return feedEntriesModel.table.toArray()
|
||||
}
|
||||
}
|
||||
/**
|
||||
* Not used yet
|
||||
*/
|
||||
export const FeedEntryService = new ServiceStatic()
|
||||
|
|
@ -1,14 +1,15 @@
|
|||
import { feedUnreadModel } from "@renderer/database/models/unread"
|
||||
import { browserDB } from "@renderer/database"
|
||||
|
||||
const feedUnreadModel = browserDB.feedUnreads
|
||||
class ServiceStatic {
|
||||
updateFeedUnread(list: [string, number][]) {
|
||||
return feedUnreadModel.table.bulkPut(
|
||||
return feedUnreadModel.bulkPut(
|
||||
list.map(([feedId, count]) => ({ id: feedId, count })),
|
||||
)
|
||||
}
|
||||
|
||||
getAll() {
|
||||
return feedUnreadModel.table.toArray() as Promise<
|
||||
return feedUnreadModel.toArray() as Promise<
|
||||
{
|
||||
id: string
|
||||
count: number
|
||||
|
|
@ -17,7 +18,11 @@ class ServiceStatic {
|
|||
}
|
||||
|
||||
clear() {
|
||||
return feedUnreadModel.table.clear()
|
||||
return feedUnreadModel.clear()
|
||||
}
|
||||
|
||||
async bulkDelete(ids: string[]) {
|
||||
return feedUnreadModel.bulkDelete(ids)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1,23 +1,31 @@
|
|||
import { feedModel } from "@renderer/database/models"
|
||||
import { browserDB } from "@renderer/database"
|
||||
import type { FeedModel } from "@renderer/models/types"
|
||||
|
||||
import { BaseService } from "./base"
|
||||
import { CleanerService } from "./cleaner"
|
||||
|
||||
type FeedModelWithId = FeedModel & { id: string }
|
||||
class ServiceStatic extends BaseService<FeedModelWithId> {
|
||||
constructor() {
|
||||
super(feedModel.table)
|
||||
super(browserDB.feeds)
|
||||
}
|
||||
|
||||
override async upsertMany(data: FeedModel[]) {
|
||||
const filterData = data.filter((d) => d.id)
|
||||
CleanerService.renew(filterData.map((d) => ({ type: "feed", id: d.id! })))
|
||||
|
||||
return this.table.bulkPut(filterData as FeedModelWithId[])
|
||||
}
|
||||
|
||||
override async upsert(data: FeedModel): Promise<string | null> {
|
||||
if (!data.id) return null
|
||||
CleanerService.renew([{ type: "feed", id: data.id }])
|
||||
return this.table.put(data as FeedModelWithId)
|
||||
}
|
||||
|
||||
async bulkDelete(ids: string[]) {
|
||||
return this.table.bulkDelete(ids)
|
||||
}
|
||||
}
|
||||
|
||||
export const FeedService = new ServiceStatic()
|
||||
|
|
|
|||
|
|
@ -1,6 +1,5 @@
|
|||
export * from "./entry"
|
||||
export * from "./entry-related"
|
||||
export * from "./feed"
|
||||
export * from "./feed-entry"
|
||||
export * from "./feed-unread"
|
||||
export * from "./subscription"
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
import { subscriptionModel } from "@renderer/database"
|
||||
import { browserDB } from "@renderer/database"
|
||||
import type { SubscriptionFlatModel } from "@renderer/store/subscription"
|
||||
import { uniq } from "lodash-es"
|
||||
|
||||
import { BaseService } from "./base"
|
||||
|
||||
|
|
@ -7,7 +8,22 @@ type SubscriptionModelWithId = SubscriptionFlatModel & { id: string }
|
|||
|
||||
class SubscriptionServiceStatic extends BaseService<SubscriptionModelWithId> {
|
||||
constructor() {
|
||||
super(subscriptionModel.table)
|
||||
super(browserDB.subscriptions)
|
||||
}
|
||||
|
||||
public getUserSubscriptions(userIds: string[]) {
|
||||
return this.table.where("userId").anyOf(userIds).toArray()
|
||||
}
|
||||
|
||||
public async getUserIds() {
|
||||
return uniq(
|
||||
(await this.table
|
||||
.toCollection()
|
||||
.uniqueKeys()
|
||||
.then((keys) =>
|
||||
keys.map((k) => k.toString().split("/")[0]),
|
||||
)) as string[],
|
||||
)
|
||||
}
|
||||
|
||||
override async upsertMany(data: SubscriptionFlatModel[]) {
|
||||
|
|
@ -33,6 +49,18 @@ class SubscriptionServiceStatic extends BaseService<SubscriptionModelWithId> {
|
|||
async changeView(feedId: string, view: number) {
|
||||
return this.table.where("feedId").equals(feedId).modify({ view })
|
||||
}
|
||||
|
||||
async removeSubscription(userId: string, feedId: string): Promise<void>
|
||||
// @ts-expect-error
|
||||
async removeSubscription(userId: string): Promise<void>
|
||||
async removeSubscription(userId: string, feedId: string) {
|
||||
if (feedId && userId) {
|
||||
return this.table.delete(this.uniqueId(userId, feedId))
|
||||
}
|
||||
if (!feedId && userId) {
|
||||
return this.table.where("userId").equals(userId).delete()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export const SubscriptionService = new SubscriptionServiceStatic()
|
||||
|
|
|
|||
|
|
@ -1,15 +1,11 @@
|
|||
import { runTransactionInScope } from "@renderer/database"
|
||||
import type { UserModel } from "@renderer/database/models/user"
|
||||
import type { EntryReadHistoriesModel } from "@renderer/hono"
|
||||
import { apiClient } from "@renderer/lib/api-fetch"
|
||||
import {
|
||||
getEntriesParams,
|
||||
omitObjectUndefinedValue,
|
||||
} from "@renderer/lib/utils"
|
||||
import type {
|
||||
CombinedEntryModel,
|
||||
EntryModel,
|
||||
FeedModel,
|
||||
import type { CombinedEntryModel, EntryModel, FeedModel, UserModel,
|
||||
} from "@renderer/models"
|
||||
import { EntryService } from "@renderer/services"
|
||||
import { produce } from "immer"
|
||||
|
|
@ -225,9 +221,8 @@ class EntryActions {
|
|||
// Update database
|
||||
runTransactionInScope(() =>
|
||||
Promise.all([
|
||||
EntryService.upsertMany(entries),
|
||||
EntryService.upsertMany(entries, entryFeedMap),
|
||||
EntryService.bulkStoreReadStatus(entry2Read),
|
||||
EntryService.bulkStoreFeedId(entryFeedMap),
|
||||
EntryService.bulkStoreCollection(entryCollection),
|
||||
]),
|
||||
)
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
import type { UserModel } from "@renderer/database/models/user"
|
||||
import type { UserModel } from "@renderer/models"
|
||||
|
||||
import { useUserStore } from "./store"
|
||||
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
import type { UserModel } from "@renderer/database/models/user"
|
||||
import type { UserModel } from "@renderer/models"
|
||||
import { produce } from "immer"
|
||||
|
||||
import { createZustandStore } from "../utils/helper"
|
||||
|
|
|
|||
|
|
@ -8,14 +8,11 @@ import { feedUnreadActions } from "../unread"
|
|||
|
||||
export const clearLocalPersistStoreData = () => {
|
||||
// All clear and reset method will aggregate here
|
||||
[
|
||||
entryActions,
|
||||
subscriptionActions,
|
||||
feedUnreadActions,
|
||||
feedActions,
|
||||
].forEach((actions) => {
|
||||
actions.clear()
|
||||
})
|
||||
[entryActions, subscriptionActions, feedUnreadActions, feedActions].forEach(
|
||||
(actions) => {
|
||||
actions.clear()
|
||||
},
|
||||
)
|
||||
|
||||
clearUISettings()
|
||||
|
||||
|
|
|
|||
|
|
@ -0,0 +1,36 @@
|
|||
import { readFileSync } from "node:fs"
|
||||
import { resolve } from "node:path"
|
||||
import { fileURLToPath } from "node:url"
|
||||
|
||||
import tsconfigPath from "vite-tsconfig-paths"
|
||||
import { defineConfig } from "vitest/config"
|
||||
|
||||
const pkg = JSON.parse(readFileSync("package.json", "utf8"))
|
||||
const __dirname = fileURLToPath(new URL(".", import.meta.url))
|
||||
export default defineConfig({
|
||||
root: "./",
|
||||
test: {
|
||||
include: ["**/*.test.ts", "**/*.spec.ts"],
|
||||
|
||||
globals: true,
|
||||
setupFiles: [resolve(__dirname, "./setup-file.ts")],
|
||||
environment: "node",
|
||||
includeSource: [resolve(__dirname, ".")],
|
||||
},
|
||||
|
||||
define: {
|
||||
APP_VERSION: JSON.stringify(pkg.version),
|
||||
APP_NAME: JSON.stringify(pkg.name),
|
||||
APP_DEV_CWD: JSON.stringify(process.cwd()),
|
||||
|
||||
GIT_COMMIT_SHA: "'SHA'",
|
||||
DEBUG: process.env.DEBUG === "true",
|
||||
ELECTRON: "false",
|
||||
},
|
||||
|
||||
plugins: [
|
||||
tsconfigPath({
|
||||
projects: [resolve(__dirname, "./tsconfig.json")],
|
||||
}),
|
||||
],
|
||||
})
|
||||
Loading…
Reference in New Issue