@talex-touch/utils 1.0.22 → 1.0.23
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/common/utils/index.ts +2 -0
- package/common/utils/timing.ts +250 -0
- package/package.json +1 -1
- package/plugin/sdk/clipboard.ts +46 -3
- package/plugin/sdk/index.ts +1 -0
- package/plugin/sdk/system.ts +14 -0
- package/plugin/sdk/types.ts +15 -0
package/common/utils/index.ts
CHANGED
|
@@ -166,6 +166,8 @@ export function structuredStrictStringify(value: unknown): string {
|
|
|
166
166
|
return JSON.stringify(serialize(value, 'root'));
|
|
167
167
|
}
|
|
168
168
|
|
|
169
|
+
export * from './timing'
|
|
170
|
+
|
|
169
171
|
export { runAdaptiveTaskQueue, type AdaptiveTaskQueueOptions } from './task-queue'
|
|
170
172
|
|
|
171
173
|
export * from './time'
|
|
@@ -0,0 +1,250 @@
|
|
|
1
|
+
export type TimingMeta = Record<string, unknown>
|
|
2
|
+
|
|
3
|
+
export interface TimingRecord {
|
|
4
|
+
label: string
|
|
5
|
+
durationMs: number
|
|
6
|
+
startedAt: number
|
|
7
|
+
endedAt: number
|
|
8
|
+
iteration?: number
|
|
9
|
+
meta?: TimingMeta
|
|
10
|
+
error?: unknown
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
export interface TimingStats {
|
|
14
|
+
label: string
|
|
15
|
+
count: number
|
|
16
|
+
totalMs: number
|
|
17
|
+
avgMs: number
|
|
18
|
+
maxMs: number
|
|
19
|
+
minMs: number
|
|
20
|
+
lastMs: number
|
|
21
|
+
lastStartedAt?: number
|
|
22
|
+
lastEndedAt?: number
|
|
23
|
+
errorCount: number
|
|
24
|
+
lastError?: unknown
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
export interface TimingSummary extends TimingStats {
|
|
28
|
+
history: TimingRecord[]
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
export interface TimingManagerConfig {
|
|
32
|
+
autoLog?: boolean
|
|
33
|
+
historyLimit?: number
|
|
34
|
+
logger?: (message: string, entry: TimingRecord, stats: TimingStats) => void
|
|
35
|
+
formatter?: (entry: TimingRecord, stats: TimingStats) => string
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
export interface TimingOptions {
|
|
39
|
+
autoLog?: boolean
|
|
40
|
+
storeHistory?: boolean
|
|
41
|
+
logger?: (message: string, entry: TimingRecord, stats: TimingStats) => void
|
|
42
|
+
formatter?: (entry: TimingRecord, stats: TimingStats) => string
|
|
43
|
+
historyLimit?: number
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
export class TimingManager {
|
|
47
|
+
private readonly stats = new Map<string, TimingStats>()
|
|
48
|
+
private readonly history = new Map<string, TimingRecord[]>()
|
|
49
|
+
private readonly moduleStats = new Map<string, TimingStats>()
|
|
50
|
+
|
|
51
|
+
constructor(private readonly config: TimingManagerConfig = {}) {}
|
|
52
|
+
|
|
53
|
+
createTiming(label: string, options: TimingOptions = {}): TimingScope {
|
|
54
|
+
return new TimingScope(this, label, options)
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
record(label: string, record: TimingRecord, options: TimingOptions = {}): void {
|
|
58
|
+
const stats = this.updateStats(this.stats, label, record)
|
|
59
|
+
const moduleKey = this.extractModuleKey(label)
|
|
60
|
+
this.updateStats(this.moduleStats, moduleKey, { ...record, label: moduleKey })
|
|
61
|
+
|
|
62
|
+
if (options.storeHistory ?? true) {
|
|
63
|
+
const limit = options.historyLimit ?? this.config.historyLimit ?? 50
|
|
64
|
+
const list = this.history.get(label) ?? []
|
|
65
|
+
list.push(record)
|
|
66
|
+
if (list.length > limit) {
|
|
67
|
+
list.splice(0, list.length - limit)
|
|
68
|
+
}
|
|
69
|
+
this.history.set(label, list)
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
const shouldLog = options.autoLog ?? this.config.autoLog ?? true
|
|
73
|
+
if (shouldLog) {
|
|
74
|
+
const formatter = options.formatter ?? this.config.formatter ?? defaultFormatter
|
|
75
|
+
const logger = options.logger ?? this.config.logger ?? defaultLogger
|
|
76
|
+
logger(formatter(record, stats), record, stats)
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
getStats(label: string): TimingStats | undefined {
|
|
81
|
+
const stats = this.stats.get(label)
|
|
82
|
+
if (!stats) return undefined
|
|
83
|
+
return { ...stats }
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
getAllStats(): TimingStats[] {
|
|
87
|
+
return Array.from(this.stats.values()).map((s) => ({ ...s }))
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
getHistory(label: string): TimingRecord[] {
|
|
91
|
+
return [...(this.history.get(label) ?? [])]
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
getModuleStats(moduleKey?: string): TimingStats[] | TimingStats | undefined {
|
|
95
|
+
if (moduleKey) {
|
|
96
|
+
const stats = this.moduleStats.get(moduleKey)
|
|
97
|
+
return stats ? { ...stats } : undefined
|
|
98
|
+
}
|
|
99
|
+
return Array.from(this.moduleStats.values()).map((s) => ({ ...s }))
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
reset(label?: string): void {
|
|
103
|
+
if (!label) {
|
|
104
|
+
this.stats.clear()
|
|
105
|
+
this.history.clear()
|
|
106
|
+
this.moduleStats.clear()
|
|
107
|
+
return
|
|
108
|
+
}
|
|
109
|
+
this.stats.delete(label)
|
|
110
|
+
this.history.delete(label)
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
private updateStats(target: Map<string, TimingStats>, label: string, record: TimingRecord): TimingStats {
|
|
114
|
+
const { durationMs, error } = record
|
|
115
|
+
const next = target.get(label) ?? {
|
|
116
|
+
label,
|
|
117
|
+
count: 0,
|
|
118
|
+
totalMs: 0,
|
|
119
|
+
avgMs: 0,
|
|
120
|
+
maxMs: Number.NEGATIVE_INFINITY,
|
|
121
|
+
minMs: Number.POSITIVE_INFINITY,
|
|
122
|
+
lastMs: 0,
|
|
123
|
+
errorCount: 0
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
next.count += 1
|
|
127
|
+
next.totalMs += durationMs
|
|
128
|
+
next.avgMs = next.totalMs / next.count
|
|
129
|
+
next.maxMs = Math.max(next.maxMs, durationMs)
|
|
130
|
+
next.minMs = Math.min(next.minMs, durationMs)
|
|
131
|
+
next.lastMs = durationMs
|
|
132
|
+
next.lastStartedAt = record.startedAt
|
|
133
|
+
next.lastEndedAt = record.endedAt
|
|
134
|
+
|
|
135
|
+
if (error) {
|
|
136
|
+
next.errorCount += 1
|
|
137
|
+
next.lastError = error
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
target.set(label, next)
|
|
141
|
+
return next
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
private extractModuleKey(label: string): string {
|
|
145
|
+
const [moduleKey] = label.split(':')
|
|
146
|
+
return moduleKey || label
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
export class TimingScope {
|
|
151
|
+
constructor(
|
|
152
|
+
private readonly manager: TimingManager,
|
|
153
|
+
private readonly label: string,
|
|
154
|
+
private readonly options: TimingOptions
|
|
155
|
+
) {}
|
|
156
|
+
|
|
157
|
+
async cost<T>(fn: () => Promise<T> | T, meta: TimingMeta = {}, overrides: TimingOptions = {}): Promise<T> {
|
|
158
|
+
const startedAt = now()
|
|
159
|
+
try {
|
|
160
|
+
const result = await fn()
|
|
161
|
+
this.finish(startedAt, meta, undefined, overrides)
|
|
162
|
+
return result
|
|
163
|
+
} catch (error) {
|
|
164
|
+
this.finish(startedAt, meta, error, overrides)
|
|
165
|
+
throw error
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
async count<T>(iterations: number, fn: (iteration: number) => Promise<T> | T, meta: TimingMeta | ((iteration: number) => TimingMeta) = {}, overrides: TimingOptions = {}): Promise<T[]> {
|
|
170
|
+
const results: T[] = []
|
|
171
|
+
for (let index = 0; index < iterations; index++) {
|
|
172
|
+
const iterationMeta = typeof meta === 'function' ? meta(index) : meta
|
|
173
|
+
const startedAt = now()
|
|
174
|
+
try {
|
|
175
|
+
const value = await fn(index)
|
|
176
|
+
this.finish(startedAt, { ...iterationMeta, iteration: index }, undefined, overrides)
|
|
177
|
+
results.push(value)
|
|
178
|
+
} catch (error) {
|
|
179
|
+
this.finish(startedAt, { ...iterationMeta, iteration: index }, error, overrides)
|
|
180
|
+
throw error
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
return results
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
mark(durationMs: number, meta: TimingMeta = {}, overrides: TimingOptions = {}): void {
|
|
187
|
+
const endedAt = now()
|
|
188
|
+
const startedAt = endedAt - durationMs
|
|
189
|
+
this.manager.record(
|
|
190
|
+
this.label,
|
|
191
|
+
{
|
|
192
|
+
label: this.label,
|
|
193
|
+
durationMs,
|
|
194
|
+
startedAt,
|
|
195
|
+
endedAt,
|
|
196
|
+
meta
|
|
197
|
+
},
|
|
198
|
+
{ ...this.options, ...overrides }
|
|
199
|
+
)
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
getStats(): TimingStats | undefined {
|
|
203
|
+
return this.manager.getStats(this.label)
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
getHistory(): TimingRecord[] {
|
|
207
|
+
return this.manager.getHistory(this.label)
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
private finish(startedAt: number, meta: TimingMeta, error: unknown, overrides: TimingOptions): void {
|
|
211
|
+
const endedAt = now()
|
|
212
|
+
const record: TimingRecord = {
|
|
213
|
+
label: this.label,
|
|
214
|
+
durationMs: endedAt - startedAt,
|
|
215
|
+
startedAt,
|
|
216
|
+
endedAt,
|
|
217
|
+
meta,
|
|
218
|
+
error
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
this.manager.record(this.label, record, { ...this.options, ...overrides })
|
|
222
|
+
}
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
function defaultFormatter(record: TimingRecord, stats: TimingStats): string {
|
|
226
|
+
const duration = record.durationMs.toFixed(2)
|
|
227
|
+
return `⏱ [${record.label}] ${duration} ms (avg: ${stats.avgMs.toFixed(2)} ms, max: ${stats.maxMs.toFixed(2)} ms, count: ${stats.count})`
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
function defaultLogger(message: string): void {
|
|
231
|
+
console.log(message)
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
const now = (() => {
|
|
235
|
+
if (typeof globalThis !== 'undefined') {
|
|
236
|
+
const perf = (globalThis as typeof globalThis & { performance?: { now?: () => number } }).performance
|
|
237
|
+
if (perf?.now) {
|
|
238
|
+
return () => perf.now()
|
|
239
|
+
}
|
|
240
|
+
}
|
|
241
|
+
return () => Date.now()
|
|
242
|
+
})()
|
|
243
|
+
|
|
244
|
+
const timingManagerInstance = new TimingManager()
|
|
245
|
+
|
|
246
|
+
export const timingManager = timingManagerInstance
|
|
247
|
+
|
|
248
|
+
export function createTiming(label: string, options: TimingOptions = {}): TimingScope {
|
|
249
|
+
return timingManagerInstance.createTiming(label, options)
|
|
250
|
+
}
|
package/package.json
CHANGED
package/plugin/sdk/clipboard.ts
CHANGED
|
@@ -8,6 +8,19 @@ const ensurePluginChannel = () => {
|
|
|
8
8
|
return channel
|
|
9
9
|
}
|
|
10
10
|
|
|
11
|
+
const normalizeItem = (item: PluginClipboardItem | null): PluginClipboardItem | null => {
|
|
12
|
+
if (!item) return item
|
|
13
|
+
if (!item.meta && typeof item.metadata === 'string') {
|
|
14
|
+
try {
|
|
15
|
+
const parsed = JSON.parse(item.metadata)
|
|
16
|
+
return { ...item, meta: parsed }
|
|
17
|
+
} catch {
|
|
18
|
+
return { ...item, meta: null }
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
return item
|
|
22
|
+
}
|
|
23
|
+
|
|
11
24
|
export interface ClipboardHistoryOptions {
|
|
12
25
|
page?: number
|
|
13
26
|
}
|
|
@@ -21,17 +34,35 @@ export interface ClipboardDeleteOptions {
|
|
|
21
34
|
id: number
|
|
22
35
|
}
|
|
23
36
|
|
|
37
|
+
export interface ClipboardApplyOptions {
|
|
38
|
+
item?: PluginClipboardItem
|
|
39
|
+
text?: string
|
|
40
|
+
html?: string | null
|
|
41
|
+
files?: string[]
|
|
42
|
+
delayMs?: number
|
|
43
|
+
hideCoreBox?: boolean
|
|
44
|
+
type?: PluginClipboardItem['type']
|
|
45
|
+
}
|
|
46
|
+
|
|
24
47
|
export function useClipboardHistory() {
|
|
25
48
|
const channel = ensurePluginChannel()
|
|
26
49
|
|
|
27
50
|
return {
|
|
28
51
|
async getLatest(): Promise<PluginClipboardItem | null> {
|
|
29
|
-
|
|
52
|
+
const result = await channel.send('clipboard:get-latest')
|
|
53
|
+
return normalizeItem(result)
|
|
30
54
|
},
|
|
31
55
|
|
|
32
56
|
async getHistory(options: ClipboardHistoryOptions = {}): Promise<PluginClipboardHistoryResponse> {
|
|
33
57
|
const { page = 1 } = options
|
|
34
|
-
|
|
58
|
+
const response = await channel.send('clipboard:get-history', { page })
|
|
59
|
+
const history = Array.isArray(response?.history)
|
|
60
|
+
? response.history.map((item: PluginClipboardItem) => normalizeItem(item) ?? item)
|
|
61
|
+
: []
|
|
62
|
+
return {
|
|
63
|
+
...response,
|
|
64
|
+
history
|
|
65
|
+
}
|
|
35
66
|
},
|
|
36
67
|
|
|
37
68
|
async setFavorite(options: ClipboardFavoriteOptions): Promise<void> {
|
|
@@ -49,8 +80,20 @@ export function useClipboardHistory() {
|
|
|
49
80
|
onDidChange(callback: (item: PluginClipboardItem) => void): () => void {
|
|
50
81
|
return channel.regChannel('core-box:clipboard-change', ({ data }) => {
|
|
51
82
|
const item = (data && 'item' in data ? data.item : data) as PluginClipboardItem
|
|
52
|
-
callback(item)
|
|
83
|
+
callback(normalizeItem(item) ?? item)
|
|
53
84
|
})
|
|
85
|
+
},
|
|
86
|
+
|
|
87
|
+
/**
|
|
88
|
+
* Writes the provided clipboard payload to the system clipboard and issues a paste command
|
|
89
|
+
* to the foreground application.
|
|
90
|
+
*/
|
|
91
|
+
async applyToActiveApp(options: ClipboardApplyOptions = {}): Promise<boolean> {
|
|
92
|
+
const response = await channel.send('clipboard:apply-to-active-app', options)
|
|
93
|
+
if (typeof response === 'object' && response) {
|
|
94
|
+
return Boolean((response as any).success)
|
|
95
|
+
}
|
|
96
|
+
return true
|
|
54
97
|
}
|
|
55
98
|
}
|
|
56
99
|
}
|
package/plugin/sdk/index.ts
CHANGED
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import type { ActiveAppSnapshot } from './types'
|
|
2
|
+
|
|
3
|
+
const ensurePluginChannel = () => {
|
|
4
|
+
const channel = (window as any)?.$channel
|
|
5
|
+
if (!channel) {
|
|
6
|
+
throw new Error('[Plugin SDK] System channel requires plugin renderer context with $channel available.')
|
|
7
|
+
}
|
|
8
|
+
return channel
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
export async function getActiveAppSnapshot(options: { forceRefresh?: boolean } = {}): Promise<ActiveAppSnapshot | null> {
|
|
12
|
+
const channel = ensurePluginChannel()
|
|
13
|
+
return channel.send('system:get-active-app', options)
|
|
14
|
+
}
|
package/plugin/sdk/types.ts
CHANGED
|
@@ -95,6 +95,8 @@ export interface PluginClipboardItem {
|
|
|
95
95
|
sourceApp?: string | null
|
|
96
96
|
timestamp?: string | number | Date | null
|
|
97
97
|
isFavorite?: boolean | null
|
|
98
|
+
metadata?: string | null
|
|
99
|
+
meta?: Record<string, unknown> | null
|
|
98
100
|
}
|
|
99
101
|
|
|
100
102
|
/**
|
|
@@ -107,6 +109,19 @@ export interface PluginClipboardHistoryResponse {
|
|
|
107
109
|
pageSize: number
|
|
108
110
|
}
|
|
109
111
|
|
|
112
|
+
export interface ActiveAppSnapshot {
|
|
113
|
+
identifier: string | null
|
|
114
|
+
displayName: string | null
|
|
115
|
+
bundleId: string | null
|
|
116
|
+
processId: number | null
|
|
117
|
+
executablePath: string | null
|
|
118
|
+
platform: 'macos' | 'windows' | 'linux' | null
|
|
119
|
+
windowTitle: string | null
|
|
120
|
+
url?: string | null
|
|
121
|
+
icon?: string | null
|
|
122
|
+
lastUpdated: number
|
|
123
|
+
}
|
|
124
|
+
|
|
110
125
|
/**
|
|
111
126
|
* Plugin utilities interface providing core functionality for plugin development
|
|
112
127
|
*
|