@talex-touch/utils 1.0.17 → 1.0.20

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.
Files changed (82) hide show
  1. package/animation/window.ts +191 -0
  2. package/channel/index.ts +49 -1
  3. package/common/index.ts +2 -0
  4. package/common/search/gather.ts +45 -0
  5. package/common/search/index.ts +67 -0
  6. package/common/storage/constants.ts +16 -2
  7. package/common/storage/entity/index.ts +2 -1
  8. package/common/storage/entity/openers.ts +32 -0
  9. package/common/storage/entity/shortcut-settings.ts +22 -0
  10. package/common/storage/shortcut-storage.ts +58 -0
  11. package/common/utils/file.ts +62 -0
  12. package/common/{utils.ts → utils/index.ts} +14 -2
  13. package/common/utils/polling.ts +184 -0
  14. package/common/utils/task-queue.ts +108 -0
  15. package/common/utils/time.ts +374 -0
  16. package/core-box/README.md +8 -8
  17. package/core-box/builder/index.ts +6 -0
  18. package/core-box/builder/tuff-builder.example.ts.bak +258 -0
  19. package/core-box/builder/tuff-builder.ts +1162 -0
  20. package/core-box/index.ts +5 -2
  21. package/core-box/run-tests.sh +7 -0
  22. package/core-box/search.ts +1 -536
  23. package/core-box/tuff/index.ts +6 -0
  24. package/core-box/tuff/tuff-dsl.ts +1412 -0
  25. package/electron/clipboard-helper.ts +199 -0
  26. package/electron/env-tool.ts +36 -2
  27. package/electron/file-parsers/index.ts +8 -0
  28. package/electron/file-parsers/parsers/text-parser.ts +109 -0
  29. package/electron/file-parsers/registry.ts +92 -0
  30. package/electron/file-parsers/types.ts +58 -0
  31. package/electron/index.ts +3 -0
  32. package/eventbus/index.ts +0 -7
  33. package/index.ts +3 -1
  34. package/package.json +4 -28
  35. package/plugin/channel.ts +48 -16
  36. package/plugin/index.ts +194 -30
  37. package/plugin/log/types.ts +11 -0
  38. package/plugin/node/index.ts +4 -0
  39. package/plugin/node/logger-manager.ts +113 -0
  40. package/plugin/{log → node}/logger.ts +41 -7
  41. package/plugin/plugin-source.ts +74 -0
  42. package/plugin/preload.ts +5 -15
  43. package/plugin/providers/index.ts +2 -0
  44. package/plugin/providers/registry.ts +47 -0
  45. package/plugin/providers/types.ts +54 -0
  46. package/plugin/risk/index.ts +1 -0
  47. package/plugin/risk/types.ts +20 -0
  48. package/plugin/sdk/enum/bridge-event.ts +4 -0
  49. package/plugin/sdk/enum/index.ts +1 -0
  50. package/plugin/sdk/hooks/bridge.ts +68 -0
  51. package/plugin/sdk/hooks/index.ts +2 -1
  52. package/plugin/sdk/hooks/life-cycle.ts +2 -4
  53. package/plugin/sdk/index.ts +2 -0
  54. package/plugin/sdk/storage.ts +84 -0
  55. package/plugin/sdk/types.ts +2 -2
  56. package/plugin/sdk/window/index.ts +5 -3
  57. package/preload/index.ts +2 -0
  58. package/preload/loading.ts +15 -0
  59. package/preload/renderer.ts +41 -0
  60. package/renderer/hooks/arg-mapper.ts +79 -0
  61. package/renderer/hooks/index.ts +2 -0
  62. package/renderer/hooks/initialize.ts +198 -0
  63. package/renderer/index.ts +3 -0
  64. package/renderer/storage/app-settings.ts +2 -0
  65. package/renderer/storage/base-storage.ts +1 -0
  66. package/renderer/storage/openers.ts +11 -0
  67. package/renderer/touch-sdk/env.ts +106 -0
  68. package/renderer/touch-sdk/index.ts +108 -0
  69. package/renderer/touch-sdk/terminal.ts +85 -0
  70. package/renderer/touch-sdk/utils.ts +61 -0
  71. package/search/levenshtein-utils.ts +39 -0
  72. package/search/types.ts +16 -16
  73. package/types/index.ts +2 -1
  74. package/types/modules/base.ts +146 -0
  75. package/types/modules/index.ts +4 -0
  76. package/types/modules/module-lifecycle.ts +148 -0
  77. package/types/modules/module-manager.ts +99 -0
  78. package/types/modules/module.ts +112 -0
  79. package/types/touch-app-core.ts +16 -93
  80. package/core-box/types.ts +0 -384
  81. package/electron/window.ts +0 -71
  82. package/plugin/log/logger-manager.ts +0 -60
@@ -0,0 +1,191 @@
1
+ import gsap from 'gsap'
2
+ import { TalexTouch } from '../types'
3
+
4
+ /**
5
+ * Window animation controller return type
6
+ */
7
+ export interface WindowAnimationController {
8
+ /**
9
+ * Update window height with animation
10
+ * @param newHeight - The new height to animate to
11
+ * @param duration - Animation duration in seconds (default: 0.5)
12
+ * @returns Promise that resolves to true if animation completed successfully, false if interrupted
13
+ */
14
+ updateHeight: (newHeight: number, duration?: number) => Promise<boolean>
15
+
16
+ /**
17
+ * Cancel current animation
18
+ * @returns Promise that resolves to true if there was an animation to cancel, false otherwise
19
+ */
20
+ cancel: () => Promise<boolean>
21
+
22
+ /**
23
+ * Toggle window visibility
24
+ * @param visible - Optional parameter to explicitly set visibility state
25
+ * @returns Promise that resolves to true if operation completed successfully, false otherwise
26
+ */
27
+ toggleWindow: (visible?: boolean) => Promise<boolean>
28
+
29
+ /**
30
+ * Change current window instance
31
+ * @param newWindow - New TouchWindow instance
32
+ */
33
+ changeWindow: (newWindow: TalexTouch.ITouchWindow) => void
34
+ }
35
+
36
+ /**
37
+ * Tracks the state of an animation
38
+ */
39
+ interface AnimationState {
40
+ tween: gsap.core.Tween | null
41
+ completed: boolean
42
+ }
43
+
44
+ /**
45
+ * Use GSAP to animate window with cubic-bezier easing
46
+ * @param window - TouchWindow instance (optional, can be set later with changeWindow)
47
+ * @returns WindowAnimationController with updateHeight, cancel, toggleWindow, and changeWindow methods
48
+ */
49
+ export function useWindowAnimation(window?: TalexTouch.ITouchWindow): WindowAnimationController {
50
+ // Store current window reference inside the function scope
51
+ let currentWindow: TalexTouch.ITouchWindow | null = window || null
52
+
53
+ const animationState: AnimationState = {
54
+ tween: null,
55
+ completed: false
56
+ }
57
+
58
+ /**
59
+ * Check if current window is valid
60
+ * @returns true if window is valid, false otherwise
61
+ */
62
+ const isWindowValid = (): boolean => {
63
+ return (
64
+ currentWindow !== null &&
65
+ currentWindow.window !== null &&
66
+ !currentWindow.window.isDestroyed()
67
+ )
68
+ }
69
+
70
+ /**
71
+ * Get current window with validation
72
+ * @returns current window or throws error if invalid
73
+ */
74
+ const getCurrentWindow = (): TalexTouch.ITouchWindow => {
75
+ if (!isWindowValid()) {
76
+ throw new Error('Window is not valid or has been destroyed')
77
+ }
78
+ return currentWindow!
79
+ }
80
+
81
+ const updateHeight = async (newHeight: number, duration: number = 0.5): Promise<boolean> => {
82
+ try {
83
+ const window = getCurrentWindow()
84
+
85
+ // Cancel any existing animation
86
+ if (animationState.tween) {
87
+ animationState.tween.kill()
88
+ }
89
+
90
+ // Reset state for new animation
91
+ animationState.completed = false
92
+
93
+ const browserWindow = window.window
94
+ const [currentWidth, currentHeight] = browserWindow.getSize()
95
+ const [x, y] = browserWindow.getPosition()
96
+
97
+ return new Promise<boolean>((resolve) => {
98
+ animationState.tween = gsap.to(
99
+ {
100
+ height: currentHeight
101
+ },
102
+ {
103
+ height: newHeight,
104
+ duration,
105
+ ease: 'cubic-bezier(0.785, 0.135, 0.15, 0.86)',
106
+ onUpdate: function () {
107
+ // Check if animation was cancelled or window destroyed
108
+ if (!animationState.tween || !isWindowValid()) {
109
+ resolve(false)
110
+ return
111
+ }
112
+
113
+ const animatedHeight = Math.round(this.targets()[0].height)
114
+ browserWindow.setSize(currentWidth, animatedHeight)
115
+ browserWindow.setPosition(x, y)
116
+ },
117
+ onComplete: () => {
118
+ animationState.tween = null
119
+ animationState.completed = true
120
+ resolve(true)
121
+ },
122
+ onKill: () => {
123
+ animationState.tween = null
124
+ resolve(false)
125
+ }
126
+ }
127
+ )
128
+ })
129
+ } catch (error) {
130
+ console.error('Error in updateHeight:', error)
131
+ return Promise.resolve(false)
132
+ }
133
+ }
134
+
135
+ const cancel = async (): Promise<boolean> => {
136
+ if (animationState.tween) {
137
+ animationState.tween.kill()
138
+ animationState.tween = null
139
+ return Promise.resolve(true)
140
+ }
141
+ return Promise.resolve(false)
142
+ }
143
+
144
+ const toggleWindow = async (visible?: boolean): Promise<boolean> => {
145
+ try {
146
+ const window = getCurrentWindow()
147
+ const browserWindow = window.window
148
+
149
+ // Determine target visibility state
150
+ const targetVisible = visible !== undefined ? visible : !browserWindow.isVisible()
151
+
152
+ if (targetVisible) {
153
+ // Show window
154
+ browserWindow.show()
155
+ } else {
156
+ // Hide window
157
+ if (process.platform === 'darwin') {
158
+ // On macOS, we can simply hide the window
159
+ browserWindow.hide()
160
+ } else {
161
+ // On other platforms, move window far off-screen before hiding
162
+ browserWindow.setPosition(-100000, -100000)
163
+ browserWindow.hide()
164
+ }
165
+ }
166
+
167
+ return Promise.resolve(true)
168
+ } catch (error) {
169
+ console.error('Error in toggleWindow:', error)
170
+ return Promise.resolve(false)
171
+ }
172
+ }
173
+
174
+ const changeWindow = (newWindow: TalexTouch.ITouchWindow): void => {
175
+ // Cancel any ongoing animation
176
+ if (animationState.tween) {
177
+ animationState.tween.kill()
178
+ animationState.tween = null
179
+ }
180
+
181
+ // Set new window
182
+ currentWindow = newWindow
183
+ }
184
+
185
+ return {
186
+ updateHeight,
187
+ cancel,
188
+ toggleWindow,
189
+ changeWindow
190
+ }
191
+ }
package/channel/index.ts CHANGED
@@ -21,6 +21,7 @@ export interface ITouchChannel {
21
21
  regChannel(type: ChannelType, eventName: string, callback: (data: StandardChannelData) => any): () => void
22
22
 
23
23
  /**
24
+ * @deprecated Use sendMain instead
24
25
  * Send a message to a channel
25
26
  * @param type {@link ChannelType} The type of channel
26
27
  * @param eventName {string} The name of event, must be unique in the channel {@link ChannelType}
@@ -29,6 +30,7 @@ export interface ITouchChannel {
29
30
  send(type: ChannelType, eventName: string, arg?: any): Promise<any>
30
31
 
31
32
  /**
33
+ * @deprecated Use sendToMain instead
32
34
  * Send a message to a channel with settled window
33
35
  * @param win {@link Electron.BrowserWindow} the window you want to sent
34
36
  * @param type {@link ChannelType} The type of channel
@@ -36,6 +38,51 @@ export interface ITouchChannel {
36
38
  * @param arg {any} The arguments of the message
37
39
  */
38
40
  sendTo(win: Electron.BrowserWindow, type: ChannelType, eventName: string, arg: any): Promise<any>
41
+
42
+ /**
43
+ * Send a message to main process
44
+ * @param eventName {string} The name of event, must be unique in the channel {@link ChannelType}
45
+ * @param arg {any} The arguments of the message
46
+ */
47
+ sendMain(eventName: string, arg?: any): Promise<any>
48
+
49
+ /**
50
+ * Send a message to main process with settled window
51
+ * @param win {@link Electron.BrowserWindow} the window you want to sent
52
+ * @param eventName {string} The name of event, must be unique in the channel {@link ChannelType}
53
+ * @param arg {any} The arguments of the message
54
+ */
55
+ sendToMain(win: Electron.BrowserWindow, eventName: string, arg?: any): Promise<any>
56
+
57
+ /**
58
+ * Send a message to all plugin process with settled window
59
+ * @param eventName {string} The name of event, must be unique in the channel {@link ChannelType}
60
+ * @param arg {any} The arguments of the message
61
+ */
62
+ sendPlugin(pluginName: string, eventName: string, arg?: any): Promise<any>
63
+
64
+ /**
65
+ * Send a message to plugin process with settled window
66
+ * @param pluginName {string} The name of plugin
67
+ * @param eventName {string} The name of event, must be unique in the channel {@link ChannelType}
68
+ * @param arg {any} The arguments of the message
69
+ */
70
+ sendToPlugin(pluginName: string, eventName: string, arg?: any): Promise<any>
71
+
72
+ /**
73
+ * Request a encrypted name key. This key cannot decrypted to get the original name.
74
+ * After use, you should revoke this key.
75
+ * @description Request a encrypted name key, and return the encrypted key
76
+ * @param name {string} The name of key
77
+ */
78
+ requestKey: (name: string) => string
79
+
80
+ /**
81
+ * Unregister a encrypted name key
82
+ * @description Unregister a encrypted name key, and return the encrypted key
83
+ * @param key {string} The encrypted key
84
+ */
85
+ revokeKey: (key: string) => boolean
39
86
  }
40
87
 
41
88
  export interface ITouchClientChannel {
@@ -84,6 +131,7 @@ export interface RawChannelHeaderData {
84
131
  status: "reply" | "request";
85
132
  type: ChannelType;
86
133
  _originData?: any;
134
+ uniqueKey?: string
87
135
  event?: Electron.IpcMainEvent | Electron.IpcRendererEvent;
88
136
  }
89
137
 
@@ -105,4 +153,4 @@ export interface StandardChannelData extends RawStandardChannelData {
105
153
 
106
154
  export type IChannelData = any //boolean | number | string | null | undefined | {
107
155
  // [prop: string]: any
108
- // }
156
+ // }
package/common/index.ts CHANGED
@@ -1,2 +1,4 @@
1
1
  export * from './storage/index'
2
2
  export * from './utils'
3
+ export * from './search'
4
+ export * from '../electron/file-parsers'
@@ -0,0 +1,45 @@
1
+ import { TuffUpdate } from "."
2
+
3
+ /**
4
+ * @interface ITuffGatherOptions
5
+ * @description Configuration options for the search result aggregator.
6
+ */
7
+ export interface ITuffGatherOptions {
8
+ /**
9
+ * The number of providers to run in parallel.
10
+ * @default 3
11
+ */
12
+ concurrency?: number
13
+ /**
14
+ * The time to wait for more results before flushing the buffer.
15
+ * @default 100
16
+ */
17
+ coalesceGapMs?: number
18
+ /**
19
+ * A shorter grace period for the first batch to ensure a quick initial response.
20
+ * @default 50
21
+ */
22
+ firstBatchGraceMs?: number
23
+ /**
24
+ * A small debounce delay for the push function to avoid rapid-fire updates.
25
+ * @default 10
26
+ */
27
+ debouncePushMs?: number
28
+ /**
29
+ * The maximum time to wait for a single provider to return results.
30
+ * @default 5000
31
+ */
32
+ taskTimeoutMs?: number
33
+ }
34
+
35
+ /**
36
+ * Defines the type signature for the real-time update callback function.
37
+ * @param update - The data object containing update information.
38
+ */
39
+ export type TuffAggregatorCallback = (update: TuffUpdate) => void
40
+
41
+ export interface IGatherController {
42
+ abort: () => void
43
+ promise: Promise<number>
44
+ signal: AbortSignal
45
+ }
@@ -0,0 +1,67 @@
1
+ import { ISearchProvider, TuffQuery, TuffSearchResult } from '@talex-touch/utils'
2
+
3
+ export * from './gather'
4
+
5
+ /**
6
+ * Represents a single update pushed from the search-gatherer.
7
+ * It provides a snapshot of the search progress at a point in time.
8
+ */
9
+ export interface TuffUpdate {
10
+ /**
11
+ * New search results from the current push batch.
12
+ * Each element is a complete TuffSearchResult from a provider.
13
+ */
14
+ newResults: TuffSearchResult[]
15
+ /**
16
+ * Total number of items aggregated so far.
17
+ */
18
+ totalCount: number
19
+ /**
20
+ * Flag indicating whether all search tasks (both default and fallback queues) have completed.
21
+ */
22
+ isDone: boolean
23
+ /**
24
+ * Flag indicating whether the search was cancelled.
25
+ */
26
+ cancelled?: boolean
27
+ /**
28
+ * Statistics about the performance of each search provider.
29
+ */
30
+ sourceStats?: TuffSearchResult['sources']
31
+ }
32
+
33
+
34
+ /**
35
+ * Search Engine Interface (formerly ISearchEngine)
36
+ *
37
+ * Defines the core functionality of the search aggregator and orchestrator.
38
+ */
39
+ export interface ISearchEngine<C> {
40
+ /**
41
+ * Registers a search provider with the engine.
42
+ * @param provider - An instance of ISearchProvider.
43
+ */
44
+ registerProvider(provider: ISearchProvider<C>): void
45
+
46
+ /**
47
+ * Unregisters a search provider by its unique ID.
48
+ * @param providerId - The unique ID of the provider to remove.
49
+ */
50
+ unregisterProvider(providerId: string): void
51
+
52
+ /**
53
+ * Executes a search across all registered and relevant providers.
54
+ * It aggregates, scores, and ranks the results.
55
+ *
56
+ * @param query - The search query object.
57
+ * @returns A promise that resolves to a TuffSearchResult object,
58
+ * containing the ranked items and metadata about the search operation.
59
+ */
60
+ search(query: TuffQuery): Promise<TuffSearchResult>
61
+
62
+ /**
63
+ * Performs background maintenance tasks, such as pre-heating caches,
64
+ * refreshing indexes, etc.
65
+ */
66
+ maintain(): void
67
+ }
@@ -1,3 +1,17 @@
1
1
  export enum StorageList {
2
- APP_SETTING = "app-setting.ini"
3
- }
2
+ APP_SETTING = 'app-setting.ini',
3
+ SHORTCUT_SETTING = 'shortcut-setting.ini',
4
+ OPENERS = 'openers.json'
5
+ }
6
+
7
+ /**
8
+ * Defines keys for the global configuration stored in the database `config` table.
9
+ * Using an enum prevents magic strings and ensures consistency across the application.
10
+ */
11
+ export enum ConfigKeys {
12
+ /**
13
+ * Stores the timestamp of the last successful full application scan using mdfind.
14
+ * This is used to schedule the next comprehensive scan.
15
+ */
16
+ APP_PROVIDER_LAST_MDFIND_SCAN = 'app_provider_last_mdfind_scan'
17
+ }
@@ -1 +1,2 @@
1
- export * from './app-settings'
1
+ export * from './app-settings'
2
+ export * from './openers'
@@ -0,0 +1,32 @@
1
+ export interface OpenerInfo {
2
+ /**
3
+ * Bundle identifier of the application responsible for handling the file type.
4
+ */
5
+ bundleId: string
6
+
7
+ /**
8
+ * Display name of the application.
9
+ */
10
+ name: string
11
+
12
+ /**
13
+ * Icon for the application (data URL or resolvable path).
14
+ */
15
+ logo: string
16
+
17
+ /**
18
+ * Absolute path of the application bundle/executable.
19
+ */
20
+ path?: string
21
+
22
+ /**
23
+ * ISO timestamp representing when this mapping was last refreshed.
24
+ */
25
+ lastResolvedAt?: string
26
+ }
27
+
28
+ export type OpenersMap = Record<string, OpenerInfo>
29
+
30
+ const _openersOriginData: OpenersMap = {}
31
+
32
+ export const openersOriginData = Object.freeze(_openersOriginData)
@@ -0,0 +1,22 @@
1
+ export enum ShortcutType {
2
+ MAIN = 'main',
3
+ RENDERER = 'renderer',
4
+ }
5
+
6
+ export interface ShortcutMeta {
7
+ creationTime: number;
8
+ modificationTime: number;
9
+ author: string;
10
+ description?: string;
11
+ }
12
+
13
+ export interface Shortcut {
14
+ id: string;
15
+ accelerator: string;
16
+ type: ShortcutType;
17
+ meta: ShortcutMeta;
18
+ }
19
+
20
+ export type ShortcutSetting = Shortcut[];
21
+
22
+ export const shortcutSettingOriginData: ShortcutSetting = [];
@@ -0,0 +1,58 @@
1
+ import { StorageList } from './constants'
2
+ import { shortcutSettingOriginData, ShortcutSetting, Shortcut } from './entity/shortcut-settings'
3
+
4
+ class ShortcutStorage {
5
+ private _config: ShortcutSetting = []
6
+
7
+ constructor(private readonly storage: {
8
+ getConfig: (name: string) => any,
9
+ saveConfig: (name: string, content?: string) => void,
10
+ }) {
11
+ this.init()
12
+ }
13
+
14
+ private init() {
15
+ const config = this.storage.getConfig(StorageList.SHORTCUT_SETTING)
16
+ if (!config || !Array.isArray(config) || config.length === 0) {
17
+ this._config = [...shortcutSettingOriginData]
18
+ this._save()
19
+ } else {
20
+ this._config = config
21
+ }
22
+ }
23
+
24
+ private _save() {
25
+ this.storage.saveConfig(StorageList.SHORTCUT_SETTING, JSON.stringify(this._config, null, 2))
26
+ }
27
+
28
+ getAllShortcuts(): Shortcut[] {
29
+ return this._config
30
+ }
31
+
32
+ getShortcutById(id: string): Shortcut | undefined {
33
+ return this._config.find(s => s.id === id)
34
+ }
35
+
36
+ addShortcut(shortcut: Shortcut): boolean {
37
+ if (this.getShortcutById(shortcut.id)) {
38
+ console.warn(`Shortcut with ID ${shortcut.id} already exists.`)
39
+ return false
40
+ }
41
+ this._config.push(shortcut)
42
+ this._save()
43
+ return true
44
+ }
45
+
46
+ updateShortcutAccelerator(id: string, newAccelerator: string): boolean {
47
+ const shortcut = this.getShortcutById(id)
48
+ if (!shortcut) {
49
+ return false
50
+ }
51
+ shortcut.accelerator = newAccelerator
52
+ shortcut.meta.modificationTime = Date.now()
53
+ this._save()
54
+ return true
55
+ }
56
+ }
57
+
58
+ export default ShortcutStorage
@@ -0,0 +1,62 @@
1
+ import path from 'path-browserify'
2
+
3
+ /**
4
+ * Enum for various file types.
5
+ * @enum {string}
6
+ */
7
+ export enum FileType {
8
+ Image = 'Image',
9
+ Document = 'Document',
10
+ Audio = 'Audio',
11
+ Video = 'Video',
12
+ Archive = 'Archive',
13
+ Code = 'Code',
14
+ Text = 'Text',
15
+ Design = 'Design',
16
+ Model3D = '3D Model',
17
+ Font = 'Font',
18
+ Spreadsheet = 'Spreadsheet',
19
+ Presentation = 'Presentation',
20
+ Ebook = 'Ebook',
21
+ Other = 'Other'
22
+ }
23
+
24
+ const extensionMap: Map<FileType, Set<string>> = new Map([
25
+ [FileType.Image, new Set(['.jpg', '.jpeg', '.png', '.gif', '.bmp', '.webp', '.svg', '.ico', '.tiff'])],
26
+ [FileType.Document, new Set(['.doc', '.docx', '.pdf', '.odt', '.rtf'])],
27
+ [FileType.Audio, new Set(['.mp3', '.wav', '.ogg', '.flac', '.aac', '.m4a'])],
28
+ [FileType.Video, new Set(['.mp4', '.avi', '.mov', '.wmv', '.mkv', '.flv', '.webm'])],
29
+ [FileType.Archive, new Set(['.zip', '.rar', '.7z', '.tar', '.gz', '.bz2'])],
30
+ [FileType.Code, new Set(['.js', '.ts', '.jsx', '.tsx', '.html', '.css', '.scss', '.json', '.xml', '.java', '.py', '.c', '.cpp', '.go', '.rs', '.php', '.sh'])],
31
+ [FileType.Text, new Set(['.txt', '.md', '.log'])],
32
+ [FileType.Design, new Set(['.psd', '.ai', '.fig', '.sketch', '.xd', '.afdesign'])],
33
+ [FileType.Model3D, new Set(['.obj', '.fbx', '.stl', '.dae', '.blend', '.3ds'])],
34
+ [FileType.Font, new Set(['.ttf', '.otf', '.woff', '.woff2'])],
35
+ [FileType.Spreadsheet, new Set(['.xls', '.xlsx', '.csv', '.numbers'])],
36
+ [FileType.Presentation, new Set(['.ppt', '.pptx', '.key'])],
37
+ [FileType.Ebook, new Set(['.epub', '.mobi', '.azw'])]
38
+ ]);
39
+
40
+ /**
41
+ * Get the file type from a file path.
42
+ * @param {string} filePath - The path to the file.
43
+ * @returns {FileType} The type of the file.
44
+ */
45
+ export function getFileTypeFromPath(filePath: string): FileType {
46
+ const extension = path.extname(filePath).toLowerCase()
47
+ return getFileTypeFromExtension(extension)
48
+ }
49
+
50
+ /**
51
+ * Get the file type from a file extension.
52
+ * @param {string} extension - The file extension, including the dot.
53
+ * @returns {FileType} The type of the file.
54
+ */
55
+ export function getFileTypeFromExtension(extension: string): FileType {
56
+ for (const [type, extensions] of extensionMap.entries()) {
57
+ if (extensions.has(extension)) {
58
+ return type
59
+ }
60
+ }
61
+ return FileType.Other
62
+ }
@@ -101,7 +101,7 @@ export function structuredStrictStringify(value: unknown): string {
101
101
  if (typeof Document !== 'undefined') {
102
102
  if (val instanceof Node) return 'DOMNode';
103
103
  }
104
- if (val instanceof Error) return 'Error';
104
+ // if (val instanceof Error) return 'Error';
105
105
  if (val instanceof WeakMap) return 'WeakMap';
106
106
  if (val instanceof WeakSet) return 'WeakSet';
107
107
  if (typeof val === 'object' && val !== null && val.constructor?.name === 'Proxy') return 'Proxy';
@@ -112,7 +112,7 @@ export function structuredStrictStringify(value: unknown): string {
112
112
  function serialize(val: any, path: string): any {
113
113
  const type = getType(val);
114
114
  // Block disallowed/unsafe types and edge cases for structured-clone
115
- if (badTypes.includes(typeof val) || type === 'DOMNode' || type === 'Error' || type === 'Proxy' || type === 'WeakMap' || type === 'WeakSet' || type === 'BigInt') {
115
+ if (badTypes.includes(typeof val) || type === 'DOMNode' || type === 'Proxy' || type === 'WeakMap' || type === 'WeakSet' || type === 'BigInt') {
116
116
  throw new Error(`Cannot serialize property at path "${path}": type "${type}"`);
117
117
  }
118
118
  // JSON doesn't support undefined, skip it for values in objects, preserve in arrays as null
@@ -130,6 +130,13 @@ export function structuredStrictStringify(value: unknown): string {
130
130
  return `[Circular ~${seen.get(val)}]`; // You could just throw if you dislike this fallback!
131
131
  }
132
132
  seen.set(val, path);
133
+ if (val instanceof Error) {
134
+ return {
135
+ name: val.name,
136
+ message: val.message,
137
+ stack: val.stack,
138
+ };
139
+ }
133
140
  if (Array.isArray(val)) {
134
141
  return val.map((item, idx) => serialize(item, `${path}[${idx}]`));
135
142
  }
@@ -158,3 +165,8 @@ export function structuredStrictStringify(value: unknown): string {
158
165
 
159
166
  return JSON.stringify(serialize(value, 'root'));
160
167
  }
168
+
169
+ export { runAdaptiveTaskQueue, type AdaptiveTaskQueueOptions } from './task-queue'
170
+
171
+ export * from './time'
172
+ export * from './file'