@wener/common 2.0.3 → 2.0.5

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 (168) hide show
  1. package/lib/ai/qwen3vl/utils.js.map +1 -1
  2. package/lib/cn/ChineseResidentIdNo.js +1 -1
  3. package/lib/cn/ChineseResidentIdNo.js.map +1 -1
  4. package/lib/cn/Mod11.js +1 -1
  5. package/lib/cn/Mod11.js.map +1 -1
  6. package/lib/consola/formatLogObject.js +5 -5
  7. package/lib/consola/formatLogObject.js.map +1 -1
  8. package/lib/data/maybeNumber.js +1 -1
  9. package/lib/data/maybeNumber.js.map +1 -1
  10. package/lib/data/types.d.js.map +1 -1
  11. package/lib/dayjs/formatDuration.js.map +1 -1
  12. package/lib/dayjs/resolveRelativeTime.js +9 -80
  13. package/lib/dayjs/resolveRelativeTime.js.map +1 -1
  14. package/lib/drain3/Drain.js +356 -0
  15. package/lib/drain3/Drain.js.map +1 -0
  16. package/lib/drain3/LogCluster.js +38 -0
  17. package/lib/drain3/LogCluster.js.map +1 -0
  18. package/lib/drain3/Node.js +39 -0
  19. package/lib/drain3/Node.js.map +1 -0
  20. package/lib/drain3/TemplateMiner.js +204 -0
  21. package/lib/drain3/TemplateMiner.js.map +1 -0
  22. package/lib/drain3/index.js +31 -0
  23. package/lib/drain3/index.js.map +1 -0
  24. package/lib/drain3/persistence/FilePersistence.js +24 -0
  25. package/lib/drain3/persistence/FilePersistence.js.map +1 -0
  26. package/lib/drain3/persistence/MemoryPersistence.js +18 -0
  27. package/lib/drain3/persistence/MemoryPersistence.js.map +1 -0
  28. package/lib/drain3/persistence/PersistenceHandler.js +5 -0
  29. package/lib/drain3/persistence/PersistenceHandler.js.map +1 -0
  30. package/lib/drain3/types.js +7 -0
  31. package/lib/drain3/types.js.map +1 -0
  32. package/lib/fs/IFileSystem.d.js.map +1 -1
  33. package/lib/fs/createBrowserFileSystem.js +4 -2
  34. package/lib/fs/createBrowserFileSystem.js.map +1 -1
  35. package/lib/fs/createMemoryFileSystem.js +7 -6
  36. package/lib/fs/createMemoryFileSystem.js.map +1 -1
  37. package/lib/fs/createSandboxFileSystem.js.map +1 -1
  38. package/lib/fs/createWebDavFileSystem.js +22 -5
  39. package/lib/fs/createWebDavFileSystem.js.map +1 -1
  40. package/lib/fs/createWebFileSystem.js +225 -0
  41. package/lib/fs/createWebFileSystem.js.map +1 -0
  42. package/lib/fs/findMimeType.js +1 -1
  43. package/lib/fs/findMimeType.js.map +1 -1
  44. package/lib/fs/index.js +1 -1
  45. package/lib/fs/index.js.map +1 -1
  46. package/lib/fs/minio/createMinioFileSystem.js +974 -0
  47. package/lib/fs/minio/createMinioFileSystem.js.map +1 -0
  48. package/lib/fs/minio/index.js +2 -0
  49. package/lib/fs/minio/index.js.map +1 -0
  50. package/lib/fs/orpc/createContractClientFileSystem.js +3 -3
  51. package/lib/fs/orpc/createContractClientFileSystem.js.map +1 -1
  52. package/lib/fs/orpc/server/createFileSystemContractImpl.js.map +1 -1
  53. package/lib/fs/s3/createS3MiniFileSystem.js +116 -68
  54. package/lib/fs/s3/createS3MiniFileSystem.js.map +1 -1
  55. package/lib/fs/server/createDatabaseFileSystem.js +7 -7
  56. package/lib/fs/server/createDatabaseFileSystem.js.map +1 -1
  57. package/lib/fs/server/createNodeFileSystem.js +30 -5
  58. package/lib/fs/server/createNodeFileSystem.js.map +1 -1
  59. package/lib/fs/tests/runFileSystemTest.js +27 -26
  60. package/lib/fs/tests/runFileSystemTest.js.map +1 -1
  61. package/lib/fs/utils.js.map +1 -1
  62. package/lib/fs/webdav/index.js +2 -0
  63. package/lib/fs/webdav/index.js.map +1 -0
  64. package/lib/jsonschema/JsonSchema.js +5 -5
  65. package/lib/jsonschema/JsonSchema.js.map +1 -1
  66. package/lib/jsonschema/forEachJsonSchema.js +1 -1
  67. package/lib/jsonschema/forEachJsonSchema.js.map +1 -1
  68. package/lib/jsonschema/types.d.js.map +1 -1
  69. package/lib/meta/defineMetadata.js.map +1 -1
  70. package/lib/orpc/createOpenApiContractClient.js.map +1 -1
  71. package/lib/password/PHC.js +2 -2
  72. package/lib/password/PHC.js.map +1 -1
  73. package/lib/password/createArgon2PasswordAlgorithm.js.map +1 -1
  74. package/lib/password/createBase64PasswordAlgorithm.js +1 -1
  75. package/lib/password/createBase64PasswordAlgorithm.js.map +1 -1
  76. package/lib/password/createBcryptPasswordAlgorithm.js.map +1 -1
  77. package/lib/password/createPBKDF2PasswordAlgorithm.js +1 -1
  78. package/lib/password/createPBKDF2PasswordAlgorithm.js.map +1 -1
  79. package/lib/password/createScryptPasswordAlgorithm.js +3 -3
  80. package/lib/password/createScryptPasswordAlgorithm.js.map +1 -1
  81. package/lib/resource/ListQuery.js.map +1 -1
  82. package/lib/resource/index.js.map +1 -1
  83. package/lib/s3/formatS3Url.js +2 -2
  84. package/lib/s3/formatS3Url.js.map +1 -1
  85. package/lib/s3/parseS3Url.js +1 -1
  86. package/lib/s3/parseS3Url.js.map +1 -1
  87. package/lib/schema/SchemaRegistry.js.map +1 -1
  88. package/lib/schema/TypeSchema.d.js.map +1 -1
  89. package/lib/schema/createSchemaData.js +4 -4
  90. package/lib/schema/createSchemaData.js.map +1 -1
  91. package/lib/schema/findJsonSchemaByPath.js.map +1 -1
  92. package/lib/schema/formatZodError.js +42 -44
  93. package/lib/schema/formatZodError.js.map +1 -1
  94. package/lib/schema/toJsonSchema.js +4 -4
  95. package/lib/schema/toJsonSchema.js.map +1 -1
  96. package/lib/schema/validate.js +1 -1
  97. package/lib/schema/validate.js.map +1 -1
  98. package/lib/utils/buildRedactorFormSchema.js +1 -1
  99. package/lib/utils/buildRedactorFormSchema.js.map +1 -1
  100. package/package.json +32 -13
  101. package/src/ai/qwen3vl/utils.ts +1 -1
  102. package/src/cn/ChineseResidentIdNo.ts +1 -1
  103. package/src/cn/Mod11.ts +1 -1
  104. package/src/cn/__snapshots__/ChineseResidentIdNo.test.ts.snap +1 -1
  105. package/src/cn/__snapshots__/UnifiedSocialCreditCode.test.ts.snap +0 -23
  106. package/src/cn/parseChineseNumber.test.ts +4 -4
  107. package/src/consola/formatLogObject.ts +6 -6
  108. package/src/data/maybeNumber.ts +1 -1
  109. package/src/data/parseSort.test.ts +0 -1
  110. package/src/data/types.d.ts +2 -2
  111. package/src/dayjs/formatDuration.ts +2 -2
  112. package/src/dayjs/resolveRelativeTime.ts +11 -14
  113. package/src/drain3/Drain.test.ts +378 -0
  114. package/src/drain3/Drain.ts +394 -0
  115. package/src/drain3/LogCluster.ts +46 -0
  116. package/src/drain3/Node.ts +53 -0
  117. package/src/drain3/TemplateMiner.ts +246 -0
  118. package/src/drain3/index.ts +36 -0
  119. package/src/drain3/persistence/FilePersistence.ts +24 -0
  120. package/src/drain3/persistence/MemoryPersistence.ts +23 -0
  121. package/src/drain3/persistence/PersistenceHandler.ts +19 -0
  122. package/src/drain3/types.ts +75 -0
  123. package/src/fs/IFileSystem.d.ts +1 -2
  124. package/src/fs/createBrowserFileSystem.ts +7 -5
  125. package/src/fs/createMemoryFileSystem.ts +9 -13
  126. package/src/fs/createSandboxFileSystem.ts +1 -1
  127. package/src/fs/createWebDavFileSystem.ts +28 -10
  128. package/src/fs/createWebFileSystem.ts +242 -0
  129. package/src/fs/findMimeType.ts +1 -4
  130. package/src/fs/index.ts +1 -1
  131. package/src/fs/minio/createMinioFileSystem.ts +1148 -0
  132. package/src/fs/minio/index.ts +1 -0
  133. package/src/fs/orpc/createContractClientFileSystem.ts +5 -5
  134. package/src/fs/orpc/server/createFileSystemContractImpl.ts +1 -1
  135. package/src/fs/s3/createS3MiniFileSystem.ts +119 -78
  136. package/src/fs/s3/s3fs.test.ts +441 -0
  137. package/src/fs/s3/s3mini.test.ts +2 -2
  138. package/src/fs/server/createDatabaseFileSystem.ts +7 -7
  139. package/src/fs/server/createNodeFileSystem.ts +32 -13
  140. package/src/fs/server/dbfs.test.ts +2 -1
  141. package/src/fs/tests/runFileSystemTest.ts +29 -28
  142. package/src/fs/utils.ts +1 -1
  143. package/src/fs/webdav/index.ts +1 -0
  144. package/src/jsonschema/JsonSchema.ts +5 -5
  145. package/src/jsonschema/forEachJsonSchema.ts +1 -1
  146. package/src/jsonschema/types.d.ts +1 -1
  147. package/src/meta/defineMetadata.ts +1 -1
  148. package/src/orpc/createOpenApiContractClient.ts +2 -2
  149. package/src/password/PHC.ts +3 -3
  150. package/src/password/createArgon2PasswordAlgorithm.ts +1 -1
  151. package/src/password/createBase64PasswordAlgorithm.ts +2 -2
  152. package/src/password/createBcryptPasswordAlgorithm.ts +4 -2
  153. package/src/password/createPBKDF2PasswordAlgorithm.ts +2 -2
  154. package/src/password/createScryptPasswordAlgorithm.ts +4 -4
  155. package/src/resource/ListQuery.ts +4 -1
  156. package/src/resource/index.ts +2 -2
  157. package/src/s3/formatS3Url.test.ts +1 -1
  158. package/src/s3/formatS3Url.ts +2 -2
  159. package/src/s3/parseS3Url.ts +1 -1
  160. package/src/schema/SchemaRegistry.ts +1 -1
  161. package/src/schema/TypeSchema.d.ts +6 -6
  162. package/src/schema/createSchemaData.ts +4 -4
  163. package/src/schema/findJsonSchemaByPath.ts +4 -4
  164. package/src/schema/formatZodError.test.ts +2 -1
  165. package/src/schema/formatZodError.ts +50 -62
  166. package/src/schema/toJsonSchema.ts +6 -6
  167. package/src/schema/validate.ts +1 -1
  168. package/src/utils/buildRedactorFormSchema.ts +3 -3
@@ -0,0 +1,246 @@
1
+ import { Drain } from './Drain';
2
+ import type { LogCluster } from './LogCluster';
3
+ import type { PersistenceHandler } from './persistence/PersistenceHandler';
4
+ import type { ClusterUpdateType, ExtractedParameter, SearchStrategy } from './types';
5
+
6
+ /**
7
+ * High-level API for log template mining with persistence support.
8
+ *
9
+ * TemplateMiner wraps the Drain algorithm and provides convenience methods
10
+ * for extracting parameters and managing persistence.
11
+ */
12
+ export class TemplateMiner {
13
+ constructor(
14
+ private drain: Drain,
15
+ private readonly persistence: PersistenceHandler,
16
+ ) {}
17
+
18
+ /**
19
+ * Adds a log message and returns the update type, cluster, template, and cluster count.
20
+ *
21
+ * @param content The log message content
22
+ * @returns Object containing updateType, cluster, templateMined, and clusterCount
23
+ */
24
+ async addLogMessage(content: string): Promise<{
25
+ updateType: ClusterUpdateType;
26
+ cluster: LogCluster;
27
+ templateMined: string;
28
+ clusterCount: number;
29
+ }> {
30
+ const { cluster, updateType } = this.drain.addLogMessage(content);
31
+
32
+ const templateMined = cluster.getTemplate();
33
+ const clusterCount = this.drain.getClusters().length;
34
+
35
+ if (updateType !== 'none') {
36
+ await this.saveState();
37
+ }
38
+
39
+ return {
40
+ updateType,
41
+ cluster,
42
+ templateMined,
43
+ clusterCount,
44
+ };
45
+ }
46
+
47
+ /**
48
+ * Matches a log message against an already existing cluster.
49
+ *
50
+ * @param content Log message to match
51
+ * @param strategy Search strategy
52
+ * @returns Matched cluster or null
53
+ */
54
+ match(content: string, strategy: SearchStrategy = 'never'): LogCluster | null {
55
+ return this.drain.match(content, strategy);
56
+ }
57
+
58
+ /**
59
+ * Extracts parameters from a log message according to a provided template.
60
+ *
61
+ * @param logTemplate Template generated by AddLogMessage
62
+ * @param logMessage The log message to extract parameters from
63
+ * @returns Array of extracted parameters, or null if template doesn't match
64
+ */
65
+ extractParameters(logTemplate: string, logMessage: string): ExtractedParameter[] | null {
66
+ // Apply delimiters
67
+ let processedMessage = logMessage;
68
+ for (const delimiter of this.drain.extraDelimiters) {
69
+ // Escape delimiter for regex if it contains special characters
70
+ const escapedDelimiter = delimiter.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
71
+ processedMessage = processedMessage.replace(new RegExp(escapedDelimiter, 'g'), ' ');
72
+ }
73
+
74
+ const { templateRegex, paramGroupNameToMaskName } = this.getTemplateParameterExtractionRegex(logTemplate);
75
+
76
+ // Create regex from template
77
+ const regex = new RegExp(templateRegex);
78
+
79
+ // Match the log message against the template
80
+ const match = processedMessage.match(regex);
81
+
82
+ // Template doesn't match
83
+ if (match === null) {
84
+ return null;
85
+ }
86
+
87
+ // Extract named captures
88
+ const extractedParameters: ExtractedParameter[] = [];
89
+
90
+ // Get all named groups from the match
91
+ if (match.groups) {
92
+ for (const [groupName, value] of Object.entries(match.groups)) {
93
+ if (value !== undefined) {
94
+ const maskName = paramGroupNameToMaskName.get(groupName);
95
+ if (maskName !== undefined) {
96
+ extractedParameters.push({
97
+ value,
98
+ maskName,
99
+ });
100
+ }
101
+ }
102
+ }
103
+ }
104
+
105
+ // Also check by index if groups are not available (fallback)
106
+ // The regex exec method provides better named group support
107
+ const execResult = regex.exec(processedMessage);
108
+ if (execResult !== null && execResult.groups === undefined) {
109
+ // Fallback: extract by order (less reliable but works)
110
+ const indices = Array.from({ length: execResult.length - 1 }, (_, i) => i + 1);
111
+ let paramIndex = 0;
112
+ for (const index of indices) {
113
+ const value = execResult[index];
114
+ if (value !== undefined) {
115
+ // Find corresponding mask name by order
116
+ // This is a fallback - ideally we should use named groups
117
+ const maskName = paramGroupNameToMaskName.get(`p_${paramIndex}`);
118
+ if (maskName !== undefined) {
119
+ extractedParameters.push({
120
+ value,
121
+ maskName,
122
+ });
123
+ }
124
+ paramIndex++;
125
+ }
126
+ }
127
+ }
128
+
129
+ return extractedParameters.length > 0 ? extractedParameters : null;
130
+ }
131
+
132
+ /**
133
+ * Gets the template parameter extraction regex and mapping.
134
+ *
135
+ * @param logTemplate The log template
136
+ * @returns Tuple of [templateRegex, paramGroupNameToMaskName]
137
+ */
138
+ private getTemplateParameterExtractionRegex(logTemplate: string): {
139
+ templateRegex: string;
140
+ paramGroupNameToMaskName: Map<string, string>;
141
+ } {
142
+ const paramGroupNameToMaskName = new Map<string, string>();
143
+ let paramNameCounter = 0;
144
+
145
+ const getNextParamName = (): string => {
146
+ const paramGroupName = `p_${paramNameCounter}`;
147
+ paramNameCounter++;
148
+ return paramGroupName;
149
+ };
150
+
151
+ // Create a named group with the respective patterns for the given mask-name
152
+ const createCaptureRegex = (maskName: string): string => {
153
+ const allowedPatterns: string[] = [];
154
+
155
+ if (maskName === '*') {
156
+ allowedPatterns.push('.+?');
157
+ }
158
+
159
+ // Give each capture group a unique name to avoid conflict
160
+ const paramGroupName = getNextParamName();
161
+ paramGroupNameToMaskName.set(paramGroupName, maskName);
162
+ const joinedPatterns = allowedPatterns.join('|');
163
+ const captureRegex = `(?<${paramGroupName}>${joinedPatterns})`;
164
+
165
+ return captureRegex;
166
+ };
167
+
168
+ // For every mask in the template, replace it with a named group
169
+ const maskNames = new Set<string>();
170
+ // The drain catch-all mask
171
+ maskNames.add('*');
172
+
173
+ // Escape the template for regex
174
+ let templateRegex = this.escapeRegex(logTemplate);
175
+
176
+ // Replace each mask name with a proper regex that captures it
177
+ for (const maskName of maskNames) {
178
+ const searchStr = `<${this.escapeRegex(maskName)}>`;
179
+
180
+ // Replace one-by-one to get a new param group name for each replacement
181
+ while (true) {
182
+ const repStr = createCaptureRegex(maskName);
183
+ const templateRegexNew = templateRegex.replace(searchStr, repStr);
184
+ // Break when all replaces for this mask are done
185
+ if (templateRegexNew === templateRegex) {
186
+ break;
187
+ }
188
+ templateRegex = templateRegexNew;
189
+ }
190
+ }
191
+
192
+ // Match also messages with multiple spaces or other whitespace chars between tokens
193
+ templateRegex = templateRegex.replace(/\\ /g, '\\s+');
194
+ templateRegex = `^${templateRegex}$`;
195
+
196
+ return { templateRegex, paramGroupNameToMaskName };
197
+ }
198
+
199
+ /**
200
+ * Escapes special regex characters in a string.
201
+ */
202
+ private escapeRegex(str: string): string {
203
+ return str.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
204
+ }
205
+
206
+ /**
207
+ * Loads the Drain state from persistence.
208
+ *
209
+ * @throws Error if loading fails
210
+ */
211
+ async loadState(): Promise<void> {
212
+ const state = await this.persistence.load();
213
+ if (state === null || (typeof state === 'string' && state.length === 0)) {
214
+ throw new Error('saved state not found');
215
+ }
216
+
217
+ try {
218
+ const stateStr = typeof state === 'string' ? state : new TextDecoder().decode(state);
219
+ const data = JSON.parse(stateStr);
220
+ this.drain = Drain.fromJSON(data);
221
+ } catch (error) {
222
+ throw new Error(`failed to unmarshal state: ${error instanceof Error ? error.message : String(error)}`);
223
+ }
224
+ }
225
+
226
+ /**
227
+ * Saves the Drain state to persistence.
228
+ *
229
+ * @throws Error if saving fails
230
+ */
231
+ async saveState(): Promise<void> {
232
+ try {
233
+ const state = JSON.stringify(this.drain.toJSON());
234
+ await this.persistence.save(state);
235
+ } catch (error) {
236
+ throw new Error(`failed to save state: ${error instanceof Error ? error.message : String(error)}`);
237
+ }
238
+ }
239
+
240
+ /**
241
+ * Gets the underlying Drain instance.
242
+ */
243
+ getDrain(): Drain {
244
+ return this.drain;
245
+ }
246
+ }
@@ -0,0 +1,36 @@
1
+ /**
2
+ * Drain3 - A TypeScript implementation of the Drain log clustering algorithm.
3
+ *
4
+ * Drain is an online log parsing algorithm that groups log messages into clusters
5
+ * based on their structural similarity, extracting templates by parameterizing
6
+ * variable parts of the logs.
7
+ *
8
+ * @example
9
+ * ```typescript
10
+ * import { Drain, TemplateMiner, MemoryPersistence } from '@wener/common/drain3';
11
+ *
12
+ * // Basic usage
13
+ * const drain = new Drain({
14
+ * logClusterDepth: 4,
15
+ * simTh: 0.4,
16
+ * maxClusters: 1000
17
+ * });
18
+ *
19
+ * const result = drain.addLogMessage('[INFO] User 123 logged in');
20
+ * console.log(result.cluster.getTemplate()); // [INFO] User <*> logged in
21
+ *
22
+ * // Advanced usage with persistence
23
+ * const miner = new TemplateMiner(drain, new MemoryPersistence());
24
+ * await miner.addLogMessage('[INFO] User 123 logged in');
25
+ * ```
26
+ */
27
+
28
+ export { Drain } from './Drain';
29
+ export { LogCluster } from './LogCluster';
30
+ export { Node } from './Node';
31
+ export { TemplateMiner } from './TemplateMiner';
32
+
33
+ export type { ClusterUpdateType, DrainOptions, ExtractedParameter, SearchStrategy } from './types';
34
+
35
+ export type { PersistenceHandler } from './persistence/PersistenceHandler';
36
+ export { MemoryPersistence } from './persistence/MemoryPersistence';
@@ -0,0 +1,24 @@
1
+ import { readFile, writeFile } from 'node:fs/promises';
2
+ import type { PersistenceHandler } from './PersistenceHandler';
3
+
4
+ /**
5
+ * File-based persistence handler that saves and loads state from the filesystem.
6
+ */
7
+ export class FilePersistence implements PersistenceHandler {
8
+ constructor(private readonly filePath: string) {}
9
+
10
+ async save(state: Uint8Array | string): Promise<void> {
11
+ await writeFile(this.filePath, state, 'utf8');
12
+ }
13
+
14
+ async load(): Promise<string | null> {
15
+ try {
16
+ return await readFile(this.filePath, 'utf8');
17
+ } catch (error) {
18
+ if ((error as { code?: string })?.code === 'ENOENT') {
19
+ return null;
20
+ }
21
+ throw error;
22
+ }
23
+ }
24
+ }
@@ -0,0 +1,23 @@
1
+ import type { PersistenceHandler } from './PersistenceHandler';
2
+
3
+ /**
4
+ * In-memory persistence handler that stores state in memory.
5
+ * Useful for testing or when persistence is not needed.
6
+ */
7
+ export class MemoryPersistence implements PersistenceHandler {
8
+ private state: Uint8Array | string | null = null;
9
+
10
+ /**
11
+ * Saves the state to memory.
12
+ */
13
+ async save(state: Uint8Array | string): Promise<void> {
14
+ this.state = state;
15
+ }
16
+
17
+ /**
18
+ * Loads the state from memory.
19
+ */
20
+ async load(): Promise<Uint8Array | string | null> {
21
+ return this.state;
22
+ }
23
+ }
@@ -0,0 +1,19 @@
1
+ /**
2
+ * Interface for persistence handlers that can save and load Drain state.
3
+ */
4
+ export interface PersistenceHandler {
5
+ /**
6
+ * Saves the Drain state.
7
+ *
8
+ * @param state Serialized state data
9
+ * @returns Promise that resolves when save is complete
10
+ */
11
+ save(state: Uint8Array | string): Promise<void>;
12
+
13
+ /**
14
+ * Loads the Drain state.
15
+ *
16
+ * @returns Promise that resolves with the loaded state, or null if no state exists
17
+ */
18
+ load(): Promise<Uint8Array | string | null>;
19
+ }
@@ -0,0 +1,75 @@
1
+ /**
2
+ * Cluster update type indicating what happened when a log message was processed.
3
+ */
4
+ export type ClusterUpdateType = 'none' | 'created' | 'templateChanged';
5
+
6
+ /**
7
+ * Search strategy for matching log messages against existing clusters.
8
+ *
9
+ * - 'never': Fastest, always performs tree search [O(log(n))] but might produce false negatives
10
+ * - 'fallback': Performs linear search [O(n)] only if tree search found no match, should not have false negatives
11
+ * - 'always': Slowest, always evaluates all clusters and selects the best match with least wildcard parameters
12
+ */
13
+ export type SearchStrategy = 'never' | 'fallback' | 'always';
14
+
15
+ /**
16
+ * Configuration options for Drain algorithm.
17
+ */
18
+ export interface DrainOptions {
19
+ /**
20
+ * Depth of the prefix tree for log clustering. Must be at least 3.
21
+ * @default 4
22
+ */
23
+ logClusterDepth?: number;
24
+
25
+ /**
26
+ * Similarity threshold (0.0 to 1.0) for matching log messages to clusters.
27
+ * @default 0.4
28
+ */
29
+ simTh?: number;
30
+
31
+ /**
32
+ * Maximum number of children nodes in the prefix tree.
33
+ * @default 100
34
+ */
35
+ maxChildren?: number;
36
+
37
+ /**
38
+ * Maximum number of clusters to maintain in LRU cache.
39
+ * @default 1000
40
+ */
41
+ maxClusters?: number;
42
+
43
+ /**
44
+ * Additional delimiters to replace with spaces during tokenization.
45
+ * @default []
46
+ */
47
+ extraDelimiters?: string[];
48
+
49
+ /**
50
+ * String used to represent parameterized tokens in templates.
51
+ * @default "<*>"
52
+ */
53
+ paramStr?: string;
54
+
55
+ /**
56
+ * Whether to automatically parameterize tokens containing numbers.
57
+ * @default true
58
+ */
59
+ parametrizeNumericTokens?: boolean;
60
+ }
61
+
62
+ /**
63
+ * Extracted parameter from a log message based on a template.
64
+ */
65
+ export interface ExtractedParameter {
66
+ /**
67
+ * The extracted parameter value.
68
+ */
69
+ value: string;
70
+
71
+ /**
72
+ * The mask name (e.g., "*") that matched this parameter.
73
+ */
74
+ maskName: string;
75
+ }
@@ -45,7 +45,7 @@ export type CreateWriteStreamOptions = OperationOptions & {
45
45
  };
46
46
  export type StatOptions = OperationOptions & {};
47
47
 
48
- type WritableData = string | ArrayBuffer | ArrayBufferView | ReadableStream;
48
+ type WritableData = string | ArrayBuffer | ArrayBufferView<ArrayBufferLike> | ReadableStream;
49
49
 
50
50
  /**
51
51
  * Universal file system interface (browser & server compatible)
@@ -99,4 +99,3 @@ export type IFileStat = {
99
99
  meta: Record<string, any>;
100
100
  size: number;
101
101
  };
102
-
@@ -37,7 +37,7 @@ type BrowserDirectory = IFileStat & {
37
37
  };
38
38
  };
39
39
 
40
- type BrowserNode = BrowserFile | BrowserDirectory;
40
+ type _BrowserNode = BrowserFile | BrowserDirectory;
41
41
 
42
42
  /**
43
43
  * Creates an IFileSystem instance backed by the browser's File System Access API.
@@ -136,7 +136,7 @@ class BrowserFS implements IFileSystem {
136
136
  return !!handle;
137
137
  }
138
138
 
139
- async readdir(dir: string, options?: ReaddirOptions): Promise<IFileStat[]> {
139
+ async readdir(dir: string, _options?: ReaddirOptions): Promise<IFileStat[]> {
140
140
  const [handle] = await this._getHandle(dir);
141
141
  if (!handle) {
142
142
  throw new BrowserFSError(`Directory not found: ${dir}`, 'ENOENT');
@@ -146,7 +146,9 @@ class BrowserFS implements IFileSystem {
146
146
  }
147
147
 
148
148
  const entries: IFileStat[] = [];
149
- for await (const entry of (handle as FileSystemDirectoryHandle).values()) {
149
+ // FileSystemDirectoryHandle.values() is part of the File System Access API
150
+ // but not yet in TypeScript's lib.dom.d.ts
151
+ for await (const entry of (handle as any).values()) {
150
152
  entries.push(await this._toFileStat(entry, join(dir, entry.name)));
151
153
  }
152
154
  return entries;
@@ -256,13 +258,13 @@ class BrowserFS implements IFileSystem {
256
258
  await writable.close();
257
259
  } else if (srcHandle.kind === 'directory') {
258
260
  const newDirHandle = await destDirHandle.getDirectoryHandle(newName, { create: true });
259
- for await (const entry of (srcHandle as FileSystemDirectoryHandle).values()) {
261
+ for await (const entry of (srcHandle as any).values()) {
260
262
  await this._copyEntry(entry, newDirHandle, entry.name);
261
263
  }
262
264
  }
263
265
  }
264
266
 
265
- async createUrl(stat: IFileStat, options?: FileUrlOptions): Promise<string> {
267
+ async createUrl(stat: IFileStat, _options?: FileUrlOptions): Promise<string> {
266
268
  // The handle is stored in the meta property in our _toFileStat method
267
269
  const handle = stat.meta.handle as FileSystemHandle | undefined;
268
270
  if (handle?.kind !== 'file') {
@@ -1,6 +1,7 @@
1
1
  import type { Readable, Writable } from 'node:stream';
2
2
  import { computeIfAbsent } from '@wener/utils';
3
3
  import { basename, dirname, normalize } from 'pathe';
4
+ import { FileSystemError } from './FileSystemError';
4
5
  import { findMimeType } from './findMimeType';
5
6
  import type {
6
7
  CopyOptions,
@@ -27,21 +28,14 @@ type MemoryDirectory = IFileStat & {
27
28
  children: MemoryNode[];
28
29
  };
29
30
 
30
- export function createMemoryFileSystem(
31
- options: {
32
- root?: MemoryDirectory;
33
- } = {},
34
- ): IFileSystem {
31
+ export function createMemoryFileSystem(options: { root?: MemoryDirectory } = {}): IFileSystem {
35
32
  return new MemFS(options);
36
33
  }
37
34
 
38
- class MemoryFileSystemError extends Error {
39
- code?: string;
40
-
41
- constructor(message: string, code?: string) {
42
- super(message);
35
+ class MemoryFileSystemError extends FileSystemError {
36
+ constructor(message: string, code: string) {
37
+ super(message, code);
43
38
  this.name = 'MemoryFileSystemError';
44
- this.code = code;
45
39
  }
46
40
  }
47
41
 
@@ -408,7 +402,7 @@ class MemFS implements IFileSystem {
408
402
 
409
403
  const self = this;
410
404
  const stream = new Writable({
411
- write(chunk: Buffer, encoding: BufferEncoding, callback: (error?: Error | null) => void) {
405
+ write(chunk: Buffer, _encoding: BufferEncoding, callback: (error?: Error | null) => void) {
412
406
  if (signal?.aborted) {
413
407
  callback(new MemoryFileSystemError('Operation aborted', 'ABORT_ERR'));
414
408
  return;
@@ -487,7 +481,9 @@ class MemFS implements IFileSystem {
487
481
  let c = computeIfAbsent(this.cache, mf, () => ({}) as Cache);
488
482
  if (!c.url) {
489
483
  let mime = findMimeType(mf.name) || 'application/octet-stream';
490
- c.url = URL.createObjectURL(new Blob([mf.content], { type: mime }));
484
+ // Convert Buffer to Uint8Array for Blob compatibility with TypeScript 5.x strict typing
485
+ const blobPart = typeof mf.content === 'string' ? mf.content : new Uint8Array(mf.content);
486
+ c.url = URL.createObjectURL(new Blob([blobPart], { type: mime }));
491
487
  }
492
488
 
493
489
  return c.url;
@@ -1,5 +1,5 @@
1
1
  import { join, normalize, relative, sep } from 'pathe';
2
- import type { IFileStat, IFileSystem, IServerFileSystem, ReadFileOptions } from './IFileSystem';
2
+ import type { IFileStat, IServerFileSystem, ReadFileOptions } from './IFileSystem';
3
3
  import { getPath } from './utils';
4
4
 
5
5
  class SandboxSecurityError extends Error {
@@ -9,6 +9,8 @@ import type {
9
9
  ReadFileOptions,
10
10
  RmOptions,
11
11
  StatOptions,
12
+ WritableData,
13
+ WriteFileOptions,
12
14
  } from './IFileSystem';
13
15
 
14
16
  export function createWebDavFileSystem({ client }: { client: MaybeFunction<WebDAVClient> }): IFileSystem {
@@ -41,10 +43,10 @@ class WebdavFS implements IFileSystem {
41
43
  const { filename: path, basename, lastmod, type: kind, etag, size, mime } = input;
42
44
  let meta: Record<string, any> = {};
43
45
  if (etag) {
44
- meta['etag'] = etag;
46
+ meta.etag = etag;
45
47
  }
46
48
  if (mime) {
47
- meta['mime'] = mime;
49
+ meta.mime = mime;
48
50
  }
49
51
  return {
50
52
  directory: path.substring(0, path.lastIndexOf('/')) || '/',
@@ -59,12 +61,12 @@ class WebdavFS implements IFileSystem {
59
61
 
60
62
  private getData<T>(input: ResponseDataDetailed<T> | T): T {
61
63
  if (
62
- input
63
- && typeof input === 'object'
64
- && 'data' in input
64
+ input &&
65
+ typeof input === 'object' &&
66
+ 'data' in input &&
65
67
  // 'headers' in input &&
66
- && 'status' in input
67
- && typeof input.status === 'number'
68
+ 'status' in input &&
69
+ typeof input.status === 'number'
68
70
  ) {
69
71
  return input.data;
70
72
  }
@@ -135,11 +137,27 @@ class WebdavFS implements IFileSystem {
135
137
  return this.getData(res);
136
138
  }
137
139
 
138
- async writeFile(path: string, data: string | Buffer | ArrayBuffer | Readable, options = {}): Promise<void> {
139
- await this.client.putFileContents(path, data, options);
140
+ async writeFile(path: string, data: WritableData, options: WriteFileOptions = {}): Promise<void> {
141
+ // Convert web ReadableStream to something WebDAV client can handle
142
+ let webdavData: string | Buffer | ArrayBuffer | Readable = data as string | Buffer | ArrayBuffer | Readable;
143
+ if (data instanceof ReadableStream) {
144
+ // Convert web ReadableStream to Buffer
145
+ const reader = data.getReader();
146
+ const chunks: Uint8Array[] = [];
147
+ while (true) {
148
+ const { done, value } = await reader.read();
149
+ if (done) break;
150
+ if (value) chunks.push(value);
151
+ }
152
+ webdavData = Buffer.concat(chunks);
153
+ } else if (ArrayBuffer.isView(data) && !(data instanceof Buffer)) {
154
+ // Convert ArrayBufferView to Buffer
155
+ webdavData = Buffer.from(data.buffer, data.byteOffset, data.byteLength);
156
+ }
157
+ await this.client.putFileContents(path, webdavData, options);
140
158
  }
141
159
 
142
- async rm(path: string, { signal, force, recursive }: RmOptions = {}): Promise<void> {
160
+ async rm(path: string, { signal: _signal, force, recursive: _recursive }: RmOptions = {}): Promise<void> {
143
161
  try {
144
162
  await this.client.deleteFile(path);
145
163
  } catch (e: any) {