-
Notifications
You must be signed in to change notification settings - Fork 57
Expand file tree
/
Copy pathindex.ts
More file actions
146 lines (117 loc) · 5.09 KB
/
index.ts
File metadata and controls
146 lines (117 loc) · 5.09 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
/**
* Copyright (c) 2021 Hengyang Zhang
*
* This software is released under the MIT License.
* https://opensource.org/licenses/MIT
*/
import { extractNamespace, isExportData, isLegacyVersion } from '@db/common/migratable'
import { StorageHolder } from '@db/common/storage-holder'
import type { BrowserMigratable, StorageMigratable } from '@db/types'
import { isOptionalInt } from '@util/guard'
import { isNotZeroResult } from '@util/stat'
import { createArrayGuard, createObjectGuard, isString } from 'typescript-guard'
import { ClassicStatDatabase, parseImportData } from './classic'
import { IDBStatDatabase } from './idb'
import type { StatCondition, StatDatabase } from './types'
type StateDatabaseComposite =
& StatDatabase
& StorageMigratable<[tabs: timer.core.Row[], groups: timer.core.Row[]]>
& BrowserMigratable<'__stat__'>
// Only `date` and `host` are required for import, other fields are optional, and will be set to default if not provided
type ValidImportRow = MakeRequired<Partial<timer.core.Row>, 'date' | 'host'>
const isValidImportRow = createObjectGuard<ValidImportRow>({
focus: isOptionalInt,
time: isOptionalInt,
run: isOptionalInt,
date: isString,
host: isString,
})
const isValidImportRows = createArrayGuard(isValidImportRow)
class StatDatabaseWrapper implements StateDatabaseComposite {
namespace: '__stat__' = '__stat__'
private holder = new StorageHolder<StatDatabase>({
classic: new ClassicStatDatabase(),
indexed_db: new IDBStatDatabase(),
})
private current = () => this.holder.current
get(host: string, date: Date | string): Promise<timer.core.Row> {
return this.current().get(host, date)
}
select(condition?: StatCondition): Promise<timer.core.Row[]> {
return this.current().select(condition)
}
accumulate(host: string, date: Date | string, item: timer.core.Result): Promise<timer.core.Result> {
return this.current().accumulate(host, date, item)
}
batchAccumulate(data: Record<string, timer.core.Result>, date: Date | string): Promise<Record<string, timer.core.Result>> {
return this.current().batchAccumulate(data, date)
}
accumulateGroup(groupId: number, date: Date | string, item: timer.core.Result): Promise<timer.core.Result> {
return this.current().accumulateGroup(groupId, date, item)
}
delete(...rows: timer.core.RowKey[]): Promise<void> {
return this.current().delete(...rows)
}
deleteByHost(host: string, range?: [start?: Date | string, end?: Date | string]): Promise<string[]> {
return this.current().deleteByHost(host, range)
}
selectGroup(condition?: StatCondition): Promise<timer.core.Row[]> {
return this.current().selectGroup(condition)
}
deleteGroup(...rows: [groupId: number, date: string][]): Promise<void> {
return this.current().deleteGroup(...rows)
}
deleteByGroup(groupId: number, range: [start?: Date | string, end?: Date | string]): Promise<void> {
return this.current().deleteByGroup(groupId, range)
}
forceUpdate(...rows: timer.core.Row[]): Promise<void> {
return this.current().forceUpdate(...rows)
}
forceUpdateGroup(...rows: timer.core.Row[]): Promise<void> {
return this.current().forceUpdateGroup(...rows)
}
async migrateStorage(type: timer.option.StorageType): Promise<[timer.core.Row[], timer.core.Row[]]> {
const target = this.holder.get(type)
if (!target) return [[], []]
const tabs = await this.select({ virtual: true })
await target.forceUpdate(...tabs)
const groups = await this.selectGroup()
await target.forceUpdateGroup(...groups)
return [tabs, groups]
}
async afterStorageMigrated([tabs, groups]: [timer.core.Row[], timer.core.Row[]]): Promise<void> {
await this.current().delete(...tabs)
const groupKeys = groups.map(({ host, date }) => [parseInt(host), date] satisfies [number, string])
await this.current().deleteGroup(...groupKeys)
}
async importData(data: unknown): Promise<void> {
const rows = this.parseImportRows(data)
await this.forceUpdate(...rows)
}
async exportData(): Promise<timer.core.Row[]> {
return this.select({ virtual: true })
}
private parseImportRows(data: unknown): timer.core.Row[] {
if (!isExportData(data)) return []
if (isLegacyVersion(data)) {
return parseImportData(data) ?? []
}
if (!(this.namespace in data)) return []
const nsData = extractNamespace(data, this.namespace, isValidImportRows) ?? []
const rows: timer.core.Row[] = []
for (const item of nsData) {
const row: timer.core.Row = {
host: item.host,
date: item.date,
time: item.time ?? 0,
focus: item.focus ?? 0,
run: item.run ?? 0,
}
isNotZeroResult(row) && rows.push(row)
}
return rows
}
}
const statDatabase: StateDatabaseComposite = new StatDatabaseWrapper()
export default statDatabase
export * from "./types"