@talex-touch/utils 1.0.18 → 1.0.21

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 (80) hide show
  1. package/channel/index.ts +49 -1
  2. package/common/index.ts +2 -0
  3. package/common/search/gather.ts +45 -0
  4. package/common/search/index.ts +67 -0
  5. package/common/storage/constants.ts +16 -2
  6. package/common/storage/entity/index.ts +2 -1
  7. package/common/storage/entity/openers.ts +32 -0
  8. package/common/storage/entity/shortcut-settings.ts +22 -0
  9. package/common/storage/shortcut-storage.ts +58 -0
  10. package/common/utils/file.ts +62 -0
  11. package/common/{utils.ts → utils/index.ts} +14 -2
  12. package/common/utils/polling.ts +184 -0
  13. package/common/utils/task-queue.ts +108 -0
  14. package/common/utils/time.ts +374 -0
  15. package/core-box/README.md +8 -8
  16. package/core-box/builder/index.ts +6 -0
  17. package/core-box/builder/tuff-builder.example.ts.bak +258 -0
  18. package/core-box/builder/tuff-builder.ts +1162 -0
  19. package/core-box/index.ts +5 -2
  20. package/core-box/run-tests.sh +7 -0
  21. package/core-box/search.ts +1 -536
  22. package/core-box/tuff/index.ts +6 -0
  23. package/core-box/tuff/tuff-dsl.ts +1412 -0
  24. package/electron/clipboard-helper.ts +199 -0
  25. package/electron/env-tool.ts +36 -2
  26. package/electron/file-parsers/index.ts +8 -0
  27. package/electron/file-parsers/parsers/text-parser.ts +109 -0
  28. package/electron/file-parsers/registry.ts +92 -0
  29. package/electron/file-parsers/types.ts +58 -0
  30. package/electron/index.ts +3 -0
  31. package/eventbus/index.ts +0 -7
  32. package/index.ts +3 -1
  33. package/package.json +4 -29
  34. package/plugin/channel.ts +48 -16
  35. package/plugin/index.ts +194 -30
  36. package/plugin/log/types.ts +11 -0
  37. package/plugin/node/index.ts +4 -0
  38. package/plugin/node/logger-manager.ts +113 -0
  39. package/plugin/{log → node}/logger.ts +41 -7
  40. package/plugin/plugin-source.ts +74 -0
  41. package/plugin/preload.ts +5 -15
  42. package/plugin/providers/index.ts +2 -0
  43. package/plugin/providers/registry.ts +47 -0
  44. package/plugin/providers/types.ts +54 -0
  45. package/plugin/risk/index.ts +1 -0
  46. package/plugin/risk/types.ts +20 -0
  47. package/plugin/sdk/enum/bridge-event.ts +4 -0
  48. package/plugin/sdk/enum/index.ts +1 -0
  49. package/plugin/sdk/hooks/bridge.ts +68 -0
  50. package/plugin/sdk/hooks/index.ts +2 -1
  51. package/plugin/sdk/hooks/life-cycle.ts +2 -4
  52. package/plugin/sdk/index.ts +2 -0
  53. package/plugin/sdk/storage.ts +84 -0
  54. package/plugin/sdk/types.ts +2 -2
  55. package/plugin/sdk/window/index.ts +5 -3
  56. package/preload/index.ts +2 -0
  57. package/preload/loading.ts +15 -0
  58. package/preload/renderer.ts +41 -0
  59. package/renderer/hooks/arg-mapper.ts +79 -0
  60. package/renderer/hooks/index.ts +2 -0
  61. package/renderer/hooks/initialize.ts +198 -0
  62. package/renderer/index.ts +3 -0
  63. package/renderer/storage/app-settings.ts +2 -0
  64. package/renderer/storage/base-storage.ts +1 -0
  65. package/renderer/storage/openers.ts +11 -0
  66. package/renderer/touch-sdk/env.ts +106 -0
  67. package/renderer/touch-sdk/index.ts +108 -0
  68. package/renderer/touch-sdk/terminal.ts +85 -0
  69. package/renderer/touch-sdk/utils.ts +61 -0
  70. package/search/levenshtein-utils.ts +39 -0
  71. package/search/types.ts +16 -16
  72. package/types/index.ts +2 -1
  73. package/types/modules/base.ts +146 -0
  74. package/types/modules/index.ts +4 -0
  75. package/types/modules/module-lifecycle.ts +148 -0
  76. package/types/modules/module-manager.ts +99 -0
  77. package/types/modules/module.ts +112 -0
  78. package/types/touch-app-core.ts +16 -93
  79. package/core-box/types.ts +0 -384
  80. package/plugin/log/logger-manager.ts +0 -60
@@ -0,0 +1,198 @@
1
+ /**
2
+ * Interface for renderer initialization information
3
+ * @interface IInitializationInfo
4
+ */
5
+ export interface IInitializationInfo {
6
+ /** Timestamp when the renderer process was initialized */
7
+ initTimestamp: number
8
+ /** ISO string of initialization time */
9
+ initTime: string
10
+ /** User agent string */
11
+ userAgent: string
12
+ /** Current URL of the renderer */
13
+ currentUrl: string
14
+ /** Operating system platform */
15
+ platform: NodeJS.Platform
16
+ /** Screen resolution information */
17
+ screenResolution: {
18
+ width: number
19
+ height: number
20
+ pixelRatio: number
21
+ }
22
+ /** Window size information */
23
+ windowSize: {
24
+ innerWidth: number
25
+ innerHeight: number
26
+ outerWidth: number
27
+ outerHeight: number
28
+ }
29
+ /** Performance timing information */
30
+ performance: {
31
+ navigationStart: number
32
+ loadEventEnd?: number
33
+ domContentLoadedEventEnd?: number
34
+ }
35
+ /** Browser/renderer capabilities */
36
+ capabilities: {
37
+ webgl: boolean
38
+ webgl2: boolean
39
+ webAudio: boolean
40
+ serviceWorker: boolean
41
+ }
42
+ }
43
+
44
+ declare global {
45
+ export interface Window {
46
+ /** Global initialization information cache */
47
+ $initInfo: IInitializationInfo
48
+ }
49
+ }
50
+
51
+ /**
52
+ * Checks WebGL support
53
+ * @returns True if WebGL is supported
54
+ */
55
+ function checkWebGLSupport(): boolean {
56
+ try {
57
+ const canvas = document.createElement('canvas')
58
+ return !!(canvas.getContext('webgl') || canvas.getContext('experimental-webgl'))
59
+ } catch {
60
+ return false
61
+ }
62
+ }
63
+
64
+ /**
65
+ * Checks WebGL2 support
66
+ * @returns True if WebGL2 is supported
67
+ */
68
+ function checkWebGL2Support(): boolean {
69
+ try {
70
+ const canvas = document.createElement('canvas')
71
+ return !!canvas.getContext('webgl2')
72
+ } catch {
73
+ return false
74
+ }
75
+ }
76
+
77
+ /**
78
+ * Checks Web Audio API support
79
+ * @returns True if Web Audio API is supported
80
+ */
81
+ function checkWebAudioSupport(): boolean {
82
+ return 'AudioContext' in window || 'webkitAudioContext' in window
83
+ }
84
+
85
+ /**
86
+ * Checks Service Worker support
87
+ * @returns True if Service Worker is supported
88
+ */
89
+ function checkServiceWorkerSupport(): boolean {
90
+ return 'serviceWorker' in navigator
91
+ }
92
+
93
+ /**
94
+ * Initializes renderer process with system and performance information
95
+ * @returns Initialization information object
96
+ */
97
+ export function useInitialize(): IInitializationInfo {
98
+ if (window.$initInfo) {
99
+ return window.$initInfo
100
+ }
101
+
102
+ const now = Date.now()
103
+ const initInfo: IInitializationInfo = {
104
+ initTimestamp: now,
105
+ initTime: new Date(now).toISOString(),
106
+ userAgent: navigator.userAgent,
107
+ currentUrl: window.location.href,
108
+ platform: process.platform,
109
+ screenResolution: {
110
+ width: screen.width,
111
+ height: screen.height,
112
+ pixelRatio: window.devicePixelRatio || 1
113
+ },
114
+ windowSize: {
115
+ innerWidth: window.innerWidth,
116
+ innerHeight: window.innerHeight,
117
+ outerWidth: window.outerWidth,
118
+ outerHeight: window.outerHeight
119
+ },
120
+ performance: {
121
+ navigationStart: performance.timeOrigin || now,
122
+ loadEventEnd: (performance.getEntriesByType('navigation')[0] as PerformanceNavigationTiming)?.loadEventEnd || undefined,
123
+ domContentLoadedEventEnd: (performance.getEntriesByType('navigation')[0] as PerformanceNavigationTiming)?.domContentLoadedEventEnd || undefined
124
+ },
125
+ capabilities: {
126
+ webgl: checkWebGLSupport(),
127
+ webgl2: checkWebGL2Support(),
128
+ webAudio: checkWebAudioSupport(),
129
+ serviceWorker: checkServiceWorkerSupport()
130
+ }
131
+ }
132
+
133
+ return window.$initInfo = initInfo
134
+ }
135
+
136
+ /**
137
+ * Gets the initialization timestamp
138
+ * @returns Timestamp when the renderer was initialized
139
+ */
140
+ export function getInitTimestamp(): number {
141
+ return useInitialize().initTimestamp
142
+ }
143
+
144
+ /**
145
+ * Gets the initialization time as ISO string
146
+ * @returns ISO string of initialization time
147
+ */
148
+ export function getInitTime(): string {
149
+ return useInitialize().initTime
150
+ }
151
+
152
+ /**
153
+ * Gets the time elapsed since initialization in milliseconds
154
+ * @returns Time elapsed since initialization
155
+ */
156
+ export function getTimeElapsed(): number {
157
+ return Date.now() - getInitTimestamp()
158
+ }
159
+
160
+ /**
161
+ * Gets screen resolution information
162
+ * @returns Screen resolution object
163
+ */
164
+ export function getScreenInfo() {
165
+ return useInitialize().screenResolution
166
+ }
167
+
168
+ /**
169
+ * Gets window size information
170
+ * @returns Window size object
171
+ */
172
+ export function getWindowInfo() {
173
+ return useInitialize().windowSize
174
+ }
175
+
176
+ /**
177
+ * Gets renderer capabilities
178
+ * @returns Capabilities object
179
+ */
180
+ export function getCapabilities() {
181
+ return useInitialize().capabilities
182
+ }
183
+
184
+ /**
185
+ * Refreshes performance timing information
186
+ * @returns Updated initialization info
187
+ */
188
+ export function refreshPerformanceInfo(): IInitializationInfo {
189
+ if (window.$initInfo) {
190
+ const navigationEntry = performance.getEntriesByType('navigation')[0] as PerformanceNavigationTiming
191
+ window.$initInfo.performance = {
192
+ navigationStart: performance.timeOrigin || window.$initInfo.performance.navigationStart,
193
+ loadEventEnd: navigationEntry?.loadEventEnd || undefined,
194
+ domContentLoadedEventEnd: navigationEntry?.domContentLoadedEventEnd || undefined
195
+ }
196
+ }
197
+ return window.$initInfo
198
+ }
package/renderer/index.ts CHANGED
@@ -1,2 +1,5 @@
1
1
  export * from './ref'
2
2
  export * from './storage'
3
+ export * from './touch-sdk'
4
+ export * from './touch-sdk/utils'
5
+ export * from './hooks'
@@ -32,3 +32,5 @@ class AppSettingsStorage extends TouchStorage<AppSetting> {
32
32
  * Global instance of the application settings
33
33
  */
34
34
  export const appSettings = new AppSettingsStorage();
35
+
36
+ console.log(appSettings)
@@ -327,6 +327,7 @@ export class TouchStorage<T extends object> {
327
327
  }
328
328
 
329
329
  const result = channel.sendSync('storage:get', this.#qualifiedName)
330
+ console.log("result", result)
330
331
  const parsed = result ? (result as Partial<T>) : {};
331
332
  this.assignData(parsed, true);
332
333
 
@@ -0,0 +1,11 @@
1
+ import { TouchStorage } from '.'
2
+ import { openersOriginData, StorageList, type OpenersMap } from '../..'
3
+
4
+ class OpenersStorage extends TouchStorage<OpenersMap> {
5
+ constructor() {
6
+ super(StorageList.OPENERS, JSON.parse(JSON.stringify(openersOriginData)))
7
+ this.setAutoSave(true)
8
+ }
9
+ }
10
+
11
+ export const openersStorage = new OpenersStorage()
@@ -0,0 +1,106 @@
1
+ import { Terminal } from './terminal'
2
+ import { ITouchClientChannel } from '@talex-touch/utils/channel'
3
+
4
+ export class EnvDetector {
5
+ private static channel: ITouchClientChannel;
6
+
7
+ public static init(channel: ITouchClientChannel) {
8
+ this.channel = channel;
9
+ }
10
+
11
+ private static async checkCommand(
12
+ command: string,
13
+ versionArgs: string = '--version',
14
+ versionRegex: RegExp = /(\d+\.\d+\.\d+)/
15
+ ): Promise<string | false> {
16
+ if (!this.channel) {
17
+ throw new Error("EnvDetector not initialized. Call EnvDetector.init(channel) first.");
18
+ }
19
+ return new Promise((resolve) => {
20
+ const terminal = new Terminal(this.channel)
21
+ let output = ''
22
+ let resolved = false;
23
+
24
+ const resolveOnce = (value: string | false) => {
25
+ if (!resolved) {
26
+ resolved = true;
27
+ resolve(value);
28
+ // For child_process, there's no explicit disconnect/kill needed after it exits.
29
+ // The process lifecycle is managed by the OS once it's started and exits.
30
+ }
31
+ }
32
+
33
+ terminal.onData((data) => {
34
+ output += data
35
+ const match = output.match(versionRegex)
36
+ if (match && match[1]) {
37
+ resolveOnce(match[1])
38
+ }
39
+ })
40
+
41
+ terminal.onExit(() => {
42
+ const match = output.match(versionRegex)
43
+ resolveOnce(match && match[1] ? match[1] : false)
44
+ })
45
+
46
+ // Execute the command with arguments
47
+ terminal.exec(command, [versionArgs]).catch(() => {
48
+ resolveOnce(false)
49
+ })
50
+
51
+ // Timeout in case the command hangs or never returns
52
+ setTimeout(() => {
53
+ resolveOnce(false)
54
+ }, 2000);
55
+ })
56
+ }
57
+
58
+ static async getNode(): Promise<string | false> {
59
+ return this.checkCommand('node')
60
+ }
61
+
62
+ static async getNpm(): Promise<string | false> {
63
+ return this.checkCommand('npm')
64
+ }
65
+
66
+ static async getGit(): Promise<string | false> {
67
+ return this.checkCommand('git')
68
+ }
69
+
70
+ static async getDegit(): Promise<boolean> {
71
+ if (!this.channel) {
72
+ throw new Error("EnvDetector not initialized. Call EnvDetector.init(channel) first.");
73
+ }
74
+ return new Promise((resolve) => {
75
+ const terminal = new Terminal(this.channel)
76
+ let receivedOutput = false;
77
+ let resolved = false;
78
+
79
+ const resolveOnce = (value: boolean) => {
80
+ if (!resolved) {
81
+ resolved = true;
82
+ resolve(value);
83
+ // For child_process, there's no explicit disconnect/kill needed after it exits.
84
+ }
85
+ }
86
+
87
+ terminal.onData(() => {
88
+ receivedOutput = true;
89
+ resolveOnce(true); // As soon as we get any output, we know it's there.
90
+ })
91
+
92
+ terminal.onExit(() => {
93
+ resolveOnce(receivedOutput)
94
+ })
95
+
96
+ // Execute degit with --help
97
+ terminal.exec('degit', ['--help']).catch(() => {
98
+ resolveOnce(false)
99
+ })
100
+
101
+ setTimeout(() => {
102
+ resolveOnce(receivedOutput);
103
+ }, 2000)
104
+ });
105
+ }
106
+ }
@@ -0,0 +1,108 @@
1
+ import { ITouchClientChannel } from '@talex-touch/utils/channel'
2
+
3
+ export interface TouchSDKOptions {
4
+ channel: ITouchClientChannel
5
+ }
6
+
7
+ export interface FolderOpenOptions {
8
+ path: string
9
+ }
10
+
11
+ export interface ExecuteCommandOptions {
12
+ command: string
13
+ }
14
+
15
+ export interface AppOpenOptions {
16
+ appName?: string
17
+ path?: string
18
+ }
19
+
20
+ export interface ExternalUrlOptions {
21
+ url: string
22
+ }
23
+
24
+ export class TouchSDK {
25
+ private channel: ITouchClientChannel
26
+
27
+ constructor(options: TouchSDKOptions) {
28
+ this.channel = options.channel
29
+ }
30
+
31
+ /**
32
+ * System Operations
33
+ */
34
+ async closeApp(): Promise<void> {
35
+ return this.channel.send('close')
36
+ }
37
+
38
+ async hideApp(): Promise<void> {
39
+ return this.channel.send('hide')
40
+ }
41
+
42
+ async minimizeApp(): Promise<void> {
43
+ return this.channel.send('minimize')
44
+ }
45
+
46
+ async openDevTools(): Promise<void> {
47
+ return this.channel.send('dev-tools')
48
+ }
49
+
50
+ async getCurrentWorkingDirectory(): Promise<string> {
51
+ return this.channel.send('common:cwd')
52
+ }
53
+
54
+ async getPackageInfo(): Promise<any> {
55
+ return this.channel.send('get-package')
56
+ }
57
+
58
+ async getOSInfo(): Promise<any> {
59
+ return this.channel.send('get-os')
60
+ }
61
+
62
+ /**
63
+ * File & Folder Operations
64
+ */
65
+ async openFolder(options: FolderOpenOptions): Promise<void> {
66
+ return this.channel.send('folder:open', options)
67
+ }
68
+
69
+ async executeCommand(options: ExecuteCommandOptions): Promise<void> {
70
+ return this.channel.send('execute:cmd', options)
71
+ }
72
+
73
+ async openApp(options: AppOpenOptions): Promise<void> {
74
+ return this.channel.send('app:open', options)
75
+ }
76
+
77
+ async openExternalUrl(options: ExternalUrlOptions): Promise<void> {
78
+ return this.channel.send('open-external', options)
79
+ }
80
+
81
+ /**
82
+ * Plugin Operations
83
+ */
84
+ async openPluginFolder(pluginName: string): Promise<void> {
85
+ return this.channel.send('plugin:explorer', pluginName)
86
+ }
87
+
88
+ /**
89
+ * Module Operations
90
+ */
91
+ async openModuleFolder(moduleName?: string): Promise<void> {
92
+ return this.channel.send('module:folder', { name: moduleName })
93
+ }
94
+
95
+ /**
96
+ * Event Registration
97
+ */
98
+ onChannelEvent(eventName: string, callback: (data: any) => void): () => void {
99
+ return this.channel.regChannel(eventName, callback)
100
+ }
101
+
102
+ /**
103
+ * Raw channel access for advanced usage
104
+ */
105
+ get rawChannel(): ITouchClientChannel {
106
+ return this.channel
107
+ }
108
+ }
@@ -0,0 +1,85 @@
1
+ import { ITouchClientChannel } from '@talex-touch/utils/channel'
2
+
3
+ type DataCallback = (data: string) => void
4
+ type ExitCallback = (exitCode: number | null) => void
5
+
6
+ export class Terminal {
7
+ private id: string | null = null
8
+ private onDataCallback: DataCallback | null = null
9
+ private onExitCallback: ExitCallback | null = null
10
+ private channel: ITouchClientChannel
11
+
12
+ constructor(channel: ITouchClientChannel) {
13
+ this.channel = channel
14
+ }
15
+
16
+ /**
17
+ * Executes a command and returns the process ID.
18
+ * The command output will be sent via the 'terminal:data' event.
19
+ * The command exit code will be sent via the 'terminal:exit' event.
20
+ * @param command The command to execute.
21
+ * @param args The arguments for the command.
22
+ * @returns A promise that resolves with the process ID.
23
+ */
24
+ public async exec(command: string, args: string[] = []): Promise<string> {
25
+ // If there's an existing process, it should ideally be killed first.
26
+ // However, for simplicity in this refactor, we'll assume exec is called for a new, independent command.
27
+ // A more robust implementation might track multiple concurrent processes.
28
+
29
+ const { id } = await this.channel.send('terminal:create', { command, args })
30
+ this.id = id
31
+
32
+ // Re-register listeners for the new process ID
33
+ this.channel.regChannel('terminal:data', (channelData) => {
34
+ if (this.id === channelData.data.id && this.onDataCallback) {
35
+ this.onDataCallback(channelData.data.data)
36
+ }
37
+ })
38
+
39
+ this.channel.regChannel('terminal:exit', (channelData) => {
40
+ if (this.id === channelData.data.id && this.onExitCallback) {
41
+ this.onExitCallback(channelData.data.exitCode)
42
+ this.id = null
43
+ }
44
+ })
45
+
46
+ return id
47
+ }
48
+
49
+ /**
50
+ * Writes data to the process stdin (if applicable).
51
+ * Note: This is less common with child_process for non-interactive commands.
52
+ * @param data The data to write.
53
+ */
54
+ public write(data: string): void {
55
+ if (this.id) {
56
+ this.channel.send('terminal:write', { id: this.id, data })
57
+ }
58
+ }
59
+
60
+ /**
61
+ * Kills the running process.
62
+ */
63
+ public kill(): void {
64
+ if (this.id) {
65
+ this.channel.send('terminal:kill', { id: this.id })
66
+ this.id = null
67
+ }
68
+ }
69
+
70
+ /**
71
+ * Sets the callback for receiving data from the process.
72
+ * @param callback The callback function.
73
+ */
74
+ public onData(callback: DataCallback): void {
75
+ this.onDataCallback = callback
76
+ }
77
+
78
+ /**
79
+ * Sets the callback for receiving the exit code from the process.
80
+ * @param callback The callback function.
81
+ */
82
+ public onExit(callback: ExitCallback): void {
83
+ this.onExitCallback = callback
84
+ }
85
+ }
@@ -0,0 +1,61 @@
1
+ import { TouchSDK, TouchSDKOptions } from './index'
2
+
3
+ /**
4
+ * Factory function to create a TouchSDK instance
5
+ */
6
+ export function createTouchSDK(options: TouchSDKOptions): TouchSDK {
7
+ return new TouchSDK(options)
8
+ }
9
+
10
+ /**
11
+ * Singleton instance for convenience
12
+ */
13
+ let sdkInstance: TouchSDK | null = null
14
+
15
+ export function initTouchSDK(options: TouchSDKOptions): TouchSDK {
16
+ sdkInstance = new TouchSDK(options)
17
+ return sdkInstance
18
+ }
19
+
20
+ export function getTouchSDK(): TouchSDK {
21
+ if (!sdkInstance) {
22
+ throw new Error('TouchSDK not initialized. Call initTouchSDK first.')
23
+ }
24
+ return sdkInstance
25
+ }
26
+
27
+ export function useTouchSDK(options?: TouchSDKOptions) {
28
+ if (!sdkInstance) {
29
+ if ( !options ) {
30
+ throw new Error('TouchSDK not initialized. Call initTouchSDK first. Cannot use hook here.')
31
+ }
32
+ initTouchSDK(options)
33
+ }
34
+
35
+ return getTouchSDK()
36
+ }
37
+
38
+ /**
39
+ * Utility functions for common operations
40
+ */
41
+ export const TouchUtils = {
42
+ async openFolder(path: string): Promise<void> {
43
+ return getTouchSDK().openFolder({ path })
44
+ },
45
+
46
+ async executeCommand(command: string): Promise<void> {
47
+ return getTouchSDK().executeCommand({ command })
48
+ },
49
+
50
+ async openApp(appName: string): Promise<void> {
51
+ return getTouchSDK().openApp({ appName })
52
+ },
53
+
54
+ async openUrl(url: string): Promise<void> {
55
+ return getTouchSDK().openExternalUrl({ url })
56
+ },
57
+
58
+ async openPluginFolder(pluginName: string): Promise<void> {
59
+ return getTouchSDK().openPluginFolder(pluginName)
60
+ }
61
+ }
@@ -0,0 +1,39 @@
1
+ /**
2
+ * Computes the Levenshtein distance between two strings.
3
+ * This is a standard dynamic programming implementation.
4
+ *
5
+ * @param s1 The first string.
6
+ * @param s2 The second string.
7
+ * @returns The Levenshtein distance.
8
+ */
9
+ export function levenshteinDistance(s1: string, s2: string): number {
10
+ const m = s1.length;
11
+ const n = s2.length;
12
+
13
+ // Create a 2D array (m+1)x(n+1) to store distances
14
+ const dp: number[][] = Array(m + 1)
15
+ .fill(0)
16
+ .map(() => Array(n + 1).fill(0));
17
+
18
+ // Initialize the DP table
19
+ for (let i = 0; i <= m; i++) {
20
+ dp[i][0] = i;
21
+ }
22
+ for (let j = 0; j <= n; j++) {
23
+ dp[0][j] = j;
24
+ }
25
+
26
+ // Fill the DP table
27
+ for (let i = 1; i <= m; i++) {
28
+ for (let j = 1; j <= n; j++) {
29
+ const cost = s1[i - 1] === s2[j - 1] ? 0 : 1;
30
+ dp[i][j] = Math.min(
31
+ dp[i - 1][j] + 1, // Deletion
32
+ dp[i][j - 1] + 1, // Insertion
33
+ dp[i - 1][j - 1] + cost // Substitution
34
+ );
35
+ }
36
+ }
37
+
38
+ return dp[m][n];
39
+ }