Skip to content

Commit dc0aff0

Browse files
committed
feat: semi
1 parent 44323bf commit dc0aff0

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

49 files changed

+1414
-670
lines changed

package.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -65,10 +65,11 @@
6565
"echarts": "^6.0.0",
6666
"element-plus": "2.13.2",
6767
"punycode": "^2.3.1",
68+
"typescript-guard": "^0.2.1",
6869
"vue": "^3.5.28",
6970
"vue-router": "^5.0.2"
7071
},
7172
"engines": {
7273
"node": ">=22"
7374
}
74-
}
75+
}

rspack/rspack.common.ts

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,6 @@ const generateJsonPlugins: RspackPluginInstance[] = []
1515

1616
const localeJsonFiles = Object.entries(i18nChrome)
1717
.map(([locale, message]) => new GenerateJsonPlugin(`_locales/${locale}/messages.json`, message))
18-
.map(plugin => plugin as unknown as RspackPluginInstance)
1918
generateJsonPlugins.push(...localeJsonFiles)
2019

2120
type EntryConfig = {

src/background/message-dispatcher.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,7 @@ class MessageDispatcher {
3333
const result = await handler(message.data, sender)
3434
return { code: 'success', data: result }
3535
} catch (error) {
36-
const msg = (error as Error)?.message ?? error?.toString?.()
36+
const msg = error instanceof Error ? error.message : error?.toString?.()
3737
return { code: 'fail', msg }
3838
}
3939
}

src/background/track-server/group.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import itemService from '@service/item-service'
22

33
function handleTabGroupRemove(group: chrome.tabGroups.TabGroup) {
4-
itemService.batchDeleteGroupById(group.id)
4+
itemService.deleteByGroup(group.id)
55
}
66

77
export function handleTabGroupEnabled() {

src/database/backup-database.ts

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -36,10 +36,6 @@ class BackupDatabase extends BaseDatabase {
3636
async updateCache(type: timer.backup.Type, newVal: unknown): Promise<void> {
3737
return this.storage.put(cacheKeyOf(type), newVal as Object)
3838
}
39-
40-
async importData(_data: any): Promise<void> {
41-
// Do nothing
42-
}
4339
}
4440

4541
const backupDatabase = new BackupDatabase()

src/database/common/base-database.ts

Lines changed: 0 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -24,12 +24,4 @@ export default abstract class BaseDatabase {
2424
protected setByKey(key: string, val: any): Promise<void> {
2525
return this.storage.put(key, val)
2626
}
27-
28-
/**
29-
* Import data
30-
*
31-
* @since 0.2.5
32-
* @param data backup data
33-
*/
34-
abstract importData(data: any): Promise<void>
3527
}
Lines changed: 185 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,185 @@
1+
const ALL_TABLES = ['stat'] as const
2+
3+
export type Table = typeof ALL_TABLES[number]
4+
5+
export type Key<T = Record<string, number>> = keyof T & string
6+
7+
type IndexConfig<T = Record<string, unknown>> = {
8+
key: Key<T> | Key<T>[]
9+
unique?: boolean
10+
}
11+
12+
export type Index<T = Record<string, unknown>> = Key<T> | Key<T>[] | IndexConfig<T>
13+
14+
const DB_NAME = `tt4b_${chrome.runtime.id}`
15+
16+
function normalizeIndex<T = Record<string, number>>(index: Index<T>): IndexConfig<T> {
17+
return typeof index === 'string' || Array.isArray(index) ? { key: index } : index
18+
}
19+
20+
function formatIdxName<T = Record<string, number>>(key: IndexConfig<T>['key']): string {
21+
const keyStr = Array.isArray(key) ? key.sort().join('_') : key
22+
return `idx_${keyStr}`
23+
}
24+
25+
export function req2Promise<T = unknown>(req: IDBRequest<T>): Promise<T | undefined> {
26+
return new Promise((resolve, reject) => {
27+
req.onsuccess = () => resolve(req.result)
28+
req.onerror = (ev) => {
29+
console.error("Failed to request indexed-db", ev, req.error)
30+
reject(req.error)
31+
}
32+
})
33+
}
34+
35+
export async function iterateCursor<T = unknown>(
36+
req: IDBRequest<IDBCursorWithValue | null>
37+
): Promise<readonly T[]>
38+
export async function iterateCursor<T = unknown>(
39+
req: IDBRequest<IDBCursorWithValue | null>,
40+
processor: (cursor: IDBCursorWithValue) => void | Promise<void>
41+
): Promise<void>
42+
43+
export async function iterateCursor<T = unknown>(
44+
req: IDBRequest<IDBCursorWithValue | null>,
45+
processor?: (cursor: IDBCursorWithValue) => void | Promise<void>
46+
): Promise<readonly T[] | void> {
47+
const collectResults = !processor
48+
const results: T[] = []
49+
50+
return new Promise((resolve, reject) => {
51+
req.onsuccess = async () => {
52+
const cursor = req.result
53+
if (!cursor) return resolve(collectResults ? results : undefined)
54+
55+
try {
56+
collectResults && results.push(cursor.value as T)
57+
await processor?.(cursor)
58+
cursor.continue()
59+
} catch (error) {
60+
reject(error)
61+
}
62+
}
63+
64+
req.onerror = () => reject(req.error)
65+
})
66+
}
67+
68+
export abstract class BaseIDBStorage<T = Record<string, unknown>> {
69+
private db: IDBDatabase | undefined
70+
abstract indexes: Index<T>[]
71+
abstract key: Key<T> | Key<T>[]
72+
abstract table: Table
73+
74+
protected async initDb(): Promise<IDBDatabase> {
75+
if (this.db) return this.db
76+
77+
const factory = typeof window === 'undefined' ? self.indexedDB : window.indexedDB
78+
const checkRequest = factory.open(DB_NAME)
79+
80+
return new Promise((resolve, reject) => {
81+
checkRequest.onsuccess = () => {
82+
const db = checkRequest.result
83+
const storeExisted = db.objectStoreNames.contains(this.table)
84+
const needUpgrade = !storeExisted || this.needUpgradeIndexes(db)
85+
86+
if (!needUpgrade) {
87+
this.db = db
88+
return resolve(db)
89+
}
90+
91+
const currentVersion = db.version
92+
db.close()
93+
94+
const upgradeRequest = factory.open(DB_NAME, currentVersion + 1)
95+
96+
upgradeRequest.onupgradeneeded = () => {
97+
const upgradeDb = upgradeRequest.result
98+
const transaction = upgradeRequest.transaction
99+
if (!transaction) return reject("Failed to get transaction of upgrading request")
100+
101+
let store = upgradeDb.objectStoreNames.contains(this.table)
102+
? transaction.objectStore(this.table)
103+
: upgradeDb.createObjectStore(this.table, { keyPath: this.key })
104+
this.createIndexes(store)
105+
}
106+
107+
upgradeRequest.onsuccess = () => {
108+
console.log("IndexedDB upgraded")
109+
this.db = upgradeRequest.result
110+
resolve(upgradeRequest.result)
111+
}
112+
113+
upgradeRequest.onerror = () => reject(upgradeRequest.error)
114+
}
115+
116+
checkRequest.onerror = () => reject(checkRequest.error)
117+
})
118+
}
119+
120+
private needUpgradeIndexes(db: IDBDatabase): boolean {
121+
try {
122+
const transaction = db.transaction(this.table, 'readonly')
123+
const store = transaction.objectStore(this.table)
124+
const indexNames = store.indexNames
125+
126+
for (const index of this.indexes) {
127+
const { key } = normalizeIndex(index)
128+
const idxName = formatIdxName(key)
129+
if (!indexNames.contains(idxName)) {
130+
return true
131+
}
132+
}
133+
return false
134+
} catch (e) {
135+
console.error("Failed to check indexes", e)
136+
return true
137+
}
138+
}
139+
140+
private createIndexes(store: IDBObjectStore) {
141+
const existingIndexes = store.indexNames
142+
143+
for (const index of this.indexes) {
144+
const { key, unique } = normalizeIndex(index)
145+
const idxName = formatIdxName(key)
146+
if (!existingIndexes.contains(idxName)) {
147+
store.createIndex(idxName, key, { unique })
148+
}
149+
}
150+
}
151+
152+
protected async withStore<T = unknown>(operation: (store: IDBObjectStore) => T | Promise<T>, mode?: IDBTransactionMode): Promise<T> {
153+
const db = await this.initDb()
154+
const trans = db.transaction(this.table, mode ?? 'readwrite')
155+
try {
156+
const store = trans.objectStore(this.table)
157+
const result = await operation(store)
158+
// Waiting for transaction completed
159+
await new Promise<void>((resolve, reject) => {
160+
trans.oncomplete = () => resolve()
161+
trans.onerror = () => reject(trans.error)
162+
trans.onabort = () => reject(new Error('Transaction aborted'))
163+
})
164+
return result
165+
} catch (e) {
166+
console.error("Failed to process with transaction", e)
167+
if (!trans.error && trans.mode !== 'readonly') {
168+
try {
169+
trans.abort()
170+
} catch (ignored) { }
171+
}
172+
throw e
173+
}
174+
}
175+
176+
protected assertIndex(store: IDBObjectStore, key: Key<T> | Key<T>[]): IDBIndex {
177+
const idxName = formatIdxName(key)
178+
try {
179+
return store.index(idxName)
180+
} catch (err) {
181+
console.error(`Failed to query index: table=${this.table}`, err)
182+
throw err
183+
}
184+
}
185+
}

src/database/common/migratable.ts

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
import type { BrowserMigratableNamespace } from '@db/types'
2+
import { isRecord } from '@util/guard'
3+
import { createObjectGuard, isInt, isString, TypeGuard } from 'typescript-guard'
4+
5+
export const isExportData = createObjectGuard<Pick<timer.backup.ExportData, '__meta__'>>({
6+
__meta__: createObjectGuard({
7+
version: isString,
8+
ts: isInt,
9+
}),
10+
})
11+
12+
export const isLegacyVersion = (data: unknown): data is timer.backup.ExportData => {
13+
if (!isExportData(data)) return false
14+
15+
const version = data.__meta__.version
16+
const match = version.match(/^(\d+)\.(\d+)\.(\d+)/)
17+
if (!match) return true
18+
19+
const major = parseInt(match[1])
20+
21+
return major < 4
22+
}
23+
24+
export const extractNamespace = <T>(data: unknown, namespace: BrowserMigratableNamespace, guard: TypeGuard<T>): T | undefined => {
25+
if (!isRecord(data)) return undefined
26+
if (!(namespace in data)) return undefined
27+
const nsData = data[namespace]
28+
return guard(nsData) ? nsData : undefined
29+
}

src/database/common/storage-promise.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -44,7 +44,7 @@ export default class StoragePromise {
4444
/**
4545
* @since 0.5.0
4646
*/
47-
put(key: string, val: Object): Promise<void> {
47+
put(key: string, val: unknown): Promise<void> {
4848
return this.set({ [key]: val })
4949
}
5050

0 commit comments

Comments
 (0)