@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.
- package/lib/ai/qwen3vl/utils.js.map +1 -1
- package/lib/cn/ChineseResidentIdNo.js +1 -1
- package/lib/cn/ChineseResidentIdNo.js.map +1 -1
- package/lib/cn/Mod11.js +1 -1
- package/lib/cn/Mod11.js.map +1 -1
- package/lib/consola/formatLogObject.js +5 -5
- package/lib/consola/formatLogObject.js.map +1 -1
- package/lib/data/maybeNumber.js +1 -1
- package/lib/data/maybeNumber.js.map +1 -1
- package/lib/data/types.d.js.map +1 -1
- package/lib/dayjs/formatDuration.js.map +1 -1
- package/lib/dayjs/resolveRelativeTime.js +9 -80
- package/lib/dayjs/resolveRelativeTime.js.map +1 -1
- package/lib/drain3/Drain.js +356 -0
- package/lib/drain3/Drain.js.map +1 -0
- package/lib/drain3/LogCluster.js +38 -0
- package/lib/drain3/LogCluster.js.map +1 -0
- package/lib/drain3/Node.js +39 -0
- package/lib/drain3/Node.js.map +1 -0
- package/lib/drain3/TemplateMiner.js +204 -0
- package/lib/drain3/TemplateMiner.js.map +1 -0
- package/lib/drain3/index.js +31 -0
- package/lib/drain3/index.js.map +1 -0
- package/lib/drain3/persistence/FilePersistence.js +24 -0
- package/lib/drain3/persistence/FilePersistence.js.map +1 -0
- package/lib/drain3/persistence/MemoryPersistence.js +18 -0
- package/lib/drain3/persistence/MemoryPersistence.js.map +1 -0
- package/lib/drain3/persistence/PersistenceHandler.js +5 -0
- package/lib/drain3/persistence/PersistenceHandler.js.map +1 -0
- package/lib/drain3/types.js +7 -0
- package/lib/drain3/types.js.map +1 -0
- package/lib/fs/IFileSystem.d.js.map +1 -1
- package/lib/fs/createBrowserFileSystem.js +4 -2
- package/lib/fs/createBrowserFileSystem.js.map +1 -1
- package/lib/fs/createMemoryFileSystem.js +7 -6
- package/lib/fs/createMemoryFileSystem.js.map +1 -1
- package/lib/fs/createSandboxFileSystem.js.map +1 -1
- package/lib/fs/createWebDavFileSystem.js +22 -5
- package/lib/fs/createWebDavFileSystem.js.map +1 -1
- package/lib/fs/createWebFileSystem.js +225 -0
- package/lib/fs/createWebFileSystem.js.map +1 -0
- package/lib/fs/findMimeType.js +1 -1
- package/lib/fs/findMimeType.js.map +1 -1
- package/lib/fs/index.js +1 -1
- package/lib/fs/index.js.map +1 -1
- package/lib/fs/minio/createMinioFileSystem.js +974 -0
- package/lib/fs/minio/createMinioFileSystem.js.map +1 -0
- package/lib/fs/minio/index.js +2 -0
- package/lib/fs/minio/index.js.map +1 -0
- package/lib/fs/orpc/createContractClientFileSystem.js +3 -3
- package/lib/fs/orpc/createContractClientFileSystem.js.map +1 -1
- package/lib/fs/orpc/server/createFileSystemContractImpl.js.map +1 -1
- package/lib/fs/s3/createS3MiniFileSystem.js +116 -68
- package/lib/fs/s3/createS3MiniFileSystem.js.map +1 -1
- package/lib/fs/server/createDatabaseFileSystem.js +7 -7
- package/lib/fs/server/createDatabaseFileSystem.js.map +1 -1
- package/lib/fs/server/createNodeFileSystem.js +30 -5
- package/lib/fs/server/createNodeFileSystem.js.map +1 -1
- package/lib/fs/tests/runFileSystemTest.js +27 -26
- package/lib/fs/tests/runFileSystemTest.js.map +1 -1
- package/lib/fs/utils.js.map +1 -1
- package/lib/fs/webdav/index.js +2 -0
- package/lib/fs/webdav/index.js.map +1 -0
- package/lib/jsonschema/JsonSchema.js +5 -5
- package/lib/jsonschema/JsonSchema.js.map +1 -1
- package/lib/jsonschema/forEachJsonSchema.js +1 -1
- package/lib/jsonschema/forEachJsonSchema.js.map +1 -1
- package/lib/jsonschema/types.d.js.map +1 -1
- package/lib/meta/defineMetadata.js.map +1 -1
- package/lib/orpc/createOpenApiContractClient.js.map +1 -1
- package/lib/password/PHC.js +2 -2
- package/lib/password/PHC.js.map +1 -1
- package/lib/password/createArgon2PasswordAlgorithm.js.map +1 -1
- package/lib/password/createBase64PasswordAlgorithm.js +1 -1
- package/lib/password/createBase64PasswordAlgorithm.js.map +1 -1
- package/lib/password/createBcryptPasswordAlgorithm.js.map +1 -1
- package/lib/password/createPBKDF2PasswordAlgorithm.js +1 -1
- package/lib/password/createPBKDF2PasswordAlgorithm.js.map +1 -1
- package/lib/password/createScryptPasswordAlgorithm.js +3 -3
- package/lib/password/createScryptPasswordAlgorithm.js.map +1 -1
- package/lib/resource/ListQuery.js.map +1 -1
- package/lib/resource/index.js.map +1 -1
- package/lib/s3/formatS3Url.js +2 -2
- package/lib/s3/formatS3Url.js.map +1 -1
- package/lib/s3/parseS3Url.js +1 -1
- package/lib/s3/parseS3Url.js.map +1 -1
- package/lib/schema/SchemaRegistry.js.map +1 -1
- package/lib/schema/TypeSchema.d.js.map +1 -1
- package/lib/schema/createSchemaData.js +4 -4
- package/lib/schema/createSchemaData.js.map +1 -1
- package/lib/schema/findJsonSchemaByPath.js.map +1 -1
- package/lib/schema/formatZodError.js +42 -44
- package/lib/schema/formatZodError.js.map +1 -1
- package/lib/schema/toJsonSchema.js +4 -4
- package/lib/schema/toJsonSchema.js.map +1 -1
- package/lib/schema/validate.js +1 -1
- package/lib/schema/validate.js.map +1 -1
- package/lib/utils/buildRedactorFormSchema.js +1 -1
- package/lib/utils/buildRedactorFormSchema.js.map +1 -1
- package/package.json +32 -13
- package/src/ai/qwen3vl/utils.ts +1 -1
- package/src/cn/ChineseResidentIdNo.ts +1 -1
- package/src/cn/Mod11.ts +1 -1
- package/src/cn/__snapshots__/ChineseResidentIdNo.test.ts.snap +1 -1
- package/src/cn/__snapshots__/UnifiedSocialCreditCode.test.ts.snap +0 -23
- package/src/cn/parseChineseNumber.test.ts +4 -4
- package/src/consola/formatLogObject.ts +6 -6
- package/src/data/maybeNumber.ts +1 -1
- package/src/data/parseSort.test.ts +0 -1
- package/src/data/types.d.ts +2 -2
- package/src/dayjs/formatDuration.ts +2 -2
- package/src/dayjs/resolveRelativeTime.ts +11 -14
- package/src/drain3/Drain.test.ts +378 -0
- package/src/drain3/Drain.ts +394 -0
- package/src/drain3/LogCluster.ts +46 -0
- package/src/drain3/Node.ts +53 -0
- package/src/drain3/TemplateMiner.ts +246 -0
- package/src/drain3/index.ts +36 -0
- package/src/drain3/persistence/FilePersistence.ts +24 -0
- package/src/drain3/persistence/MemoryPersistence.ts +23 -0
- package/src/drain3/persistence/PersistenceHandler.ts +19 -0
- package/src/drain3/types.ts +75 -0
- package/src/fs/IFileSystem.d.ts +1 -2
- package/src/fs/createBrowserFileSystem.ts +7 -5
- package/src/fs/createMemoryFileSystem.ts +9 -13
- package/src/fs/createSandboxFileSystem.ts +1 -1
- package/src/fs/createWebDavFileSystem.ts +28 -10
- package/src/fs/createWebFileSystem.ts +242 -0
- package/src/fs/findMimeType.ts +1 -4
- package/src/fs/index.ts +1 -1
- package/src/fs/minio/createMinioFileSystem.ts +1148 -0
- package/src/fs/minio/index.ts +1 -0
- package/src/fs/orpc/createContractClientFileSystem.ts +5 -5
- package/src/fs/orpc/server/createFileSystemContractImpl.ts +1 -1
- package/src/fs/s3/createS3MiniFileSystem.ts +119 -78
- package/src/fs/s3/s3fs.test.ts +441 -0
- package/src/fs/s3/s3mini.test.ts +2 -2
- package/src/fs/server/createDatabaseFileSystem.ts +7 -7
- package/src/fs/server/createNodeFileSystem.ts +32 -13
- package/src/fs/server/dbfs.test.ts +2 -1
- package/src/fs/tests/runFileSystemTest.ts +29 -28
- package/src/fs/utils.ts +1 -1
- package/src/fs/webdav/index.ts +1 -0
- package/src/jsonschema/JsonSchema.ts +5 -5
- package/src/jsonschema/forEachJsonSchema.ts +1 -1
- package/src/jsonschema/types.d.ts +1 -1
- package/src/meta/defineMetadata.ts +1 -1
- package/src/orpc/createOpenApiContractClient.ts +2 -2
- package/src/password/PHC.ts +3 -3
- package/src/password/createArgon2PasswordAlgorithm.ts +1 -1
- package/src/password/createBase64PasswordAlgorithm.ts +2 -2
- package/src/password/createBcryptPasswordAlgorithm.ts +4 -2
- package/src/password/createPBKDF2PasswordAlgorithm.ts +2 -2
- package/src/password/createScryptPasswordAlgorithm.ts +4 -4
- package/src/resource/ListQuery.ts +4 -1
- package/src/resource/index.ts +2 -2
- package/src/s3/formatS3Url.test.ts +1 -1
- package/src/s3/formatS3Url.ts +2 -2
- package/src/s3/parseS3Url.ts +1 -1
- package/src/schema/SchemaRegistry.ts +1 -1
- package/src/schema/TypeSchema.d.ts +6 -6
- package/src/schema/createSchemaData.ts +4 -4
- package/src/schema/findJsonSchemaByPath.ts +4 -4
- package/src/schema/formatZodError.test.ts +2 -1
- package/src/schema/formatZodError.ts +50 -62
- package/src/schema/toJsonSchema.ts +6 -6
- package/src/schema/validate.ts +1 -1
- package/src/utils/buildRedactorFormSchema.ts +3 -3
|
@@ -0,0 +1,204 @@
|
|
|
1
|
+
import { Drain } from "./Drain.js";
|
|
2
|
+
/**
|
|
3
|
+
* High-level API for log template mining with persistence support.
|
|
4
|
+
*
|
|
5
|
+
* TemplateMiner wraps the Drain algorithm and provides convenience methods
|
|
6
|
+
* for extracting parameters and managing persistence.
|
|
7
|
+
*/ export class TemplateMiner {
|
|
8
|
+
drain;
|
|
9
|
+
persistence;
|
|
10
|
+
constructor(drain, persistence) {
|
|
11
|
+
this.drain = drain;
|
|
12
|
+
this.persistence = persistence;
|
|
13
|
+
}
|
|
14
|
+
/**
|
|
15
|
+
* Adds a log message and returns the update type, cluster, template, and cluster count.
|
|
16
|
+
*
|
|
17
|
+
* @param content The log message content
|
|
18
|
+
* @returns Object containing updateType, cluster, templateMined, and clusterCount
|
|
19
|
+
*/ async addLogMessage(content) {
|
|
20
|
+
const { cluster, updateType } = this.drain.addLogMessage(content);
|
|
21
|
+
const templateMined = cluster.getTemplate();
|
|
22
|
+
const clusterCount = this.drain.getClusters().length;
|
|
23
|
+
if (updateType !== "none") {
|
|
24
|
+
await this.saveState();
|
|
25
|
+
}
|
|
26
|
+
return {
|
|
27
|
+
updateType,
|
|
28
|
+
cluster,
|
|
29
|
+
templateMined,
|
|
30
|
+
clusterCount
|
|
31
|
+
};
|
|
32
|
+
}
|
|
33
|
+
/**
|
|
34
|
+
* Matches a log message against an already existing cluster.
|
|
35
|
+
*
|
|
36
|
+
* @param content Log message to match
|
|
37
|
+
* @param strategy Search strategy
|
|
38
|
+
* @returns Matched cluster or null
|
|
39
|
+
*/ match(content, strategy = "never") {
|
|
40
|
+
return this.drain.match(content, strategy);
|
|
41
|
+
}
|
|
42
|
+
/**
|
|
43
|
+
* Extracts parameters from a log message according to a provided template.
|
|
44
|
+
*
|
|
45
|
+
* @param logTemplate Template generated by AddLogMessage
|
|
46
|
+
* @param logMessage The log message to extract parameters from
|
|
47
|
+
* @returns Array of extracted parameters, or null if template doesn't match
|
|
48
|
+
*/ extractParameters(logTemplate, logMessage) {
|
|
49
|
+
// Apply delimiters
|
|
50
|
+
let processedMessage = logMessage;
|
|
51
|
+
for (const delimiter of this.drain.extraDelimiters) {
|
|
52
|
+
// Escape delimiter for regex if it contains special characters
|
|
53
|
+
const escapedDelimiter = delimiter.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
54
|
+
processedMessage = processedMessage.replace(new RegExp(escapedDelimiter, "g"), " ");
|
|
55
|
+
}
|
|
56
|
+
const { templateRegex, paramGroupNameToMaskName } = this.getTemplateParameterExtractionRegex(logTemplate);
|
|
57
|
+
// Create regex from template
|
|
58
|
+
const regex = new RegExp(templateRegex);
|
|
59
|
+
// Match the log message against the template
|
|
60
|
+
const match = processedMessage.match(regex);
|
|
61
|
+
// Template doesn't match
|
|
62
|
+
if (match === null) {
|
|
63
|
+
return null;
|
|
64
|
+
}
|
|
65
|
+
// Extract named captures
|
|
66
|
+
const extractedParameters = [];
|
|
67
|
+
// Get all named groups from the match
|
|
68
|
+
if (match.groups) {
|
|
69
|
+
for (const [groupName, value] of Object.entries(match.groups)) {
|
|
70
|
+
if (value !== undefined) {
|
|
71
|
+
const maskName = paramGroupNameToMaskName.get(groupName);
|
|
72
|
+
if (maskName !== undefined) {
|
|
73
|
+
extractedParameters.push({
|
|
74
|
+
value,
|
|
75
|
+
maskName
|
|
76
|
+
});
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
// Also check by index if groups are not available (fallback)
|
|
82
|
+
// The regex exec method provides better named group support
|
|
83
|
+
const execResult = regex.exec(processedMessage);
|
|
84
|
+
if (execResult !== null && execResult.groups === undefined) {
|
|
85
|
+
// Fallback: extract by order (less reliable but works)
|
|
86
|
+
const indices = Array.from({
|
|
87
|
+
length: execResult.length - 1
|
|
88
|
+
}, (_, i) => i + 1);
|
|
89
|
+
let paramIndex = 0;
|
|
90
|
+
for (const index of indices) {
|
|
91
|
+
const value = execResult[index];
|
|
92
|
+
if (value !== undefined) {
|
|
93
|
+
// Find corresponding mask name by order
|
|
94
|
+
// This is a fallback - ideally we should use named groups
|
|
95
|
+
const maskName = paramGroupNameToMaskName.get(`p_${paramIndex}`);
|
|
96
|
+
if (maskName !== undefined) {
|
|
97
|
+
extractedParameters.push({
|
|
98
|
+
value,
|
|
99
|
+
maskName
|
|
100
|
+
});
|
|
101
|
+
}
|
|
102
|
+
paramIndex++;
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
return extractedParameters.length > 0 ? extractedParameters : null;
|
|
107
|
+
}
|
|
108
|
+
/**
|
|
109
|
+
* Gets the template parameter extraction regex and mapping.
|
|
110
|
+
*
|
|
111
|
+
* @param logTemplate The log template
|
|
112
|
+
* @returns Tuple of [templateRegex, paramGroupNameToMaskName]
|
|
113
|
+
*/ getTemplateParameterExtractionRegex(logTemplate) {
|
|
114
|
+
const paramGroupNameToMaskName = new Map();
|
|
115
|
+
let paramNameCounter = 0;
|
|
116
|
+
const getNextParamName = () => {
|
|
117
|
+
const paramGroupName = `p_${paramNameCounter}`;
|
|
118
|
+
paramNameCounter++;
|
|
119
|
+
return paramGroupName;
|
|
120
|
+
};
|
|
121
|
+
// Create a named group with the respective patterns for the given mask-name
|
|
122
|
+
const createCaptureRegex = (maskName) => {
|
|
123
|
+
const allowedPatterns = [];
|
|
124
|
+
if (maskName === "*") {
|
|
125
|
+
allowedPatterns.push(".+?");
|
|
126
|
+
}
|
|
127
|
+
// Give each capture group a unique name to avoid conflict
|
|
128
|
+
const paramGroupName = getNextParamName();
|
|
129
|
+
paramGroupNameToMaskName.set(paramGroupName, maskName);
|
|
130
|
+
const joinedPatterns = allowedPatterns.join("|");
|
|
131
|
+
const captureRegex = `(?<${paramGroupName}>${joinedPatterns})`;
|
|
132
|
+
return captureRegex;
|
|
133
|
+
};
|
|
134
|
+
// For every mask in the template, replace it with a named group
|
|
135
|
+
const maskNames = new Set();
|
|
136
|
+
// The drain catch-all mask
|
|
137
|
+
maskNames.add("*");
|
|
138
|
+
// Escape the template for regex
|
|
139
|
+
let templateRegex = this.escapeRegex(logTemplate);
|
|
140
|
+
// Replace each mask name with a proper regex that captures it
|
|
141
|
+
for (const maskName of maskNames) {
|
|
142
|
+
const searchStr = `<${this.escapeRegex(maskName)}>`;
|
|
143
|
+
// Replace one-by-one to get a new param group name for each replacement
|
|
144
|
+
while (true) {
|
|
145
|
+
const repStr = createCaptureRegex(maskName);
|
|
146
|
+
const templateRegexNew = templateRegex.replace(searchStr, repStr);
|
|
147
|
+
// Break when all replaces for this mask are done
|
|
148
|
+
if (templateRegexNew === templateRegex) {
|
|
149
|
+
break;
|
|
150
|
+
}
|
|
151
|
+
templateRegex = templateRegexNew;
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
// Match also messages with multiple spaces or other whitespace chars between tokens
|
|
155
|
+
templateRegex = templateRegex.replace(/\\ /g, "\\s+");
|
|
156
|
+
templateRegex = `^${templateRegex}$`;
|
|
157
|
+
return {
|
|
158
|
+
templateRegex,
|
|
159
|
+
paramGroupNameToMaskName
|
|
160
|
+
};
|
|
161
|
+
}
|
|
162
|
+
/**
|
|
163
|
+
* Escapes special regex characters in a string.
|
|
164
|
+
*/ escapeRegex(str) {
|
|
165
|
+
return str.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
166
|
+
}
|
|
167
|
+
/**
|
|
168
|
+
* Loads the Drain state from persistence.
|
|
169
|
+
*
|
|
170
|
+
* @throws Error if loading fails
|
|
171
|
+
*/ async loadState() {
|
|
172
|
+
const state = await this.persistence.load();
|
|
173
|
+
if (state === null || typeof state === "string" && state.length === 0) {
|
|
174
|
+
throw new Error("saved state not found");
|
|
175
|
+
}
|
|
176
|
+
try {
|
|
177
|
+
const stateStr = typeof state === "string" ? state : new TextDecoder().decode(state);
|
|
178
|
+
const data = JSON.parse(stateStr);
|
|
179
|
+
this.drain = Drain.fromJSON(data);
|
|
180
|
+
}
|
|
181
|
+
catch (error) {
|
|
182
|
+
throw new Error(`failed to unmarshal state: ${error instanceof Error ? error.message : String(error)}`);
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
/**
|
|
186
|
+
* Saves the Drain state to persistence.
|
|
187
|
+
*
|
|
188
|
+
* @throws Error if saving fails
|
|
189
|
+
*/ async saveState() {
|
|
190
|
+
try {
|
|
191
|
+
const state = JSON.stringify(this.drain.toJSON());
|
|
192
|
+
await this.persistence.save(state);
|
|
193
|
+
}
|
|
194
|
+
catch (error) {
|
|
195
|
+
throw new Error(`failed to save state: ${error instanceof Error ? error.message : String(error)}`);
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
/**
|
|
199
|
+
* Gets the underlying Drain instance.
|
|
200
|
+
*/ getDrain() {
|
|
201
|
+
return this.drain;
|
|
202
|
+
}
|
|
203
|
+
}
|
|
204
|
+
//# sourceMappingURL=TemplateMiner.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../src/drain3/TemplateMiner.ts"],"sourcesContent":["import { Drain } from './Drain';\nimport type { LogCluster } from './LogCluster';\nimport type { PersistenceHandler } from './persistence/PersistenceHandler';\nimport type { ClusterUpdateType, ExtractedParameter, SearchStrategy } from './types';\n\n/**\n * High-level API for log template mining with persistence support.\n *\n * TemplateMiner wraps the Drain algorithm and provides convenience methods\n * for extracting parameters and managing persistence.\n */\nexport class TemplateMiner {\n\tconstructor(\n\t\tprivate drain: Drain,\n\t\tprivate readonly persistence: PersistenceHandler,\n\t) {}\n\n\t/**\n\t * Adds a log message and returns the update type, cluster, template, and cluster count.\n\t *\n\t * @param content The log message content\n\t * @returns Object containing updateType, cluster, templateMined, and clusterCount\n\t */\n\tasync addLogMessage(content: string): Promise<{\n\t\tupdateType: ClusterUpdateType;\n\t\tcluster: LogCluster;\n\t\ttemplateMined: string;\n\t\tclusterCount: number;\n\t}> {\n\t\tconst { cluster, updateType } = this.drain.addLogMessage(content);\n\n\t\tconst templateMined = cluster.getTemplate();\n\t\tconst clusterCount = this.drain.getClusters().length;\n\n\t\tif (updateType !== 'none') {\n\t\t\tawait this.saveState();\n\t\t}\n\n\t\treturn {\n\t\t\tupdateType,\n\t\t\tcluster,\n\t\t\ttemplateMined,\n\t\t\tclusterCount,\n\t\t};\n\t}\n\n\t/**\n\t * Matches a log message against an already existing cluster.\n\t *\n\t * @param content Log message to match\n\t * @param strategy Search strategy\n\t * @returns Matched cluster or null\n\t */\n\tmatch(content: string, strategy: SearchStrategy = 'never'): LogCluster | null {\n\t\treturn this.drain.match(content, strategy);\n\t}\n\n\t/**\n\t * Extracts parameters from a log message according to a provided template.\n\t *\n\t * @param logTemplate Template generated by AddLogMessage\n\t * @param logMessage The log message to extract parameters from\n\t * @returns Array of extracted parameters, or null if template doesn't match\n\t */\n\textractParameters(logTemplate: string, logMessage: string): ExtractedParameter[] | null {\n\t\t// Apply delimiters\n\t\tlet processedMessage = logMessage;\n\t\tfor (const delimiter of this.drain.extraDelimiters) {\n\t\t\t// Escape delimiter for regex if it contains special characters\n\t\t\tconst escapedDelimiter = delimiter.replace(/[.*+?^${}()|[\\]\\\\]/g, '\\\\$&');\n\t\t\tprocessedMessage = processedMessage.replace(new RegExp(escapedDelimiter, 'g'), ' ');\n\t\t}\n\n\t\tconst { templateRegex, paramGroupNameToMaskName } = this.getTemplateParameterExtractionRegex(logTemplate);\n\n\t\t// Create regex from template\n\t\tconst regex = new RegExp(templateRegex);\n\n\t\t// Match the log message against the template\n\t\tconst match = processedMessage.match(regex);\n\n\t\t// Template doesn't match\n\t\tif (match === null) {\n\t\t\treturn null;\n\t\t}\n\n\t\t// Extract named captures\n\t\tconst extractedParameters: ExtractedParameter[] = [];\n\n\t\t// Get all named groups from the match\n\t\tif (match.groups) {\n\t\t\tfor (const [groupName, value] of Object.entries(match.groups)) {\n\t\t\t\tif (value !== undefined) {\n\t\t\t\t\tconst maskName = paramGroupNameToMaskName.get(groupName);\n\t\t\t\t\tif (maskName !== undefined) {\n\t\t\t\t\t\textractedParameters.push({\n\t\t\t\t\t\t\tvalue,\n\t\t\t\t\t\t\tmaskName,\n\t\t\t\t\t\t});\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\t// Also check by index if groups are not available (fallback)\n\t\t// The regex exec method provides better named group support\n\t\tconst execResult = regex.exec(processedMessage);\n\t\tif (execResult !== null && execResult.groups === undefined) {\n\t\t\t// Fallback: extract by order (less reliable but works)\n\t\t\tconst indices = Array.from({ length: execResult.length - 1 }, (_, i) => i + 1);\n\t\t\tlet paramIndex = 0;\n\t\t\tfor (const index of indices) {\n\t\t\t\tconst value = execResult[index];\n\t\t\t\tif (value !== undefined) {\n\t\t\t\t\t// Find corresponding mask name by order\n\t\t\t\t\t// This is a fallback - ideally we should use named groups\n\t\t\t\t\tconst maskName = paramGroupNameToMaskName.get(`p_${paramIndex}`);\n\t\t\t\t\tif (maskName !== undefined) {\n\t\t\t\t\t\textractedParameters.push({\n\t\t\t\t\t\t\tvalue,\n\t\t\t\t\t\t\tmaskName,\n\t\t\t\t\t\t});\n\t\t\t\t\t}\n\t\t\t\t\tparamIndex++;\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\treturn extractedParameters.length > 0 ? extractedParameters : null;\n\t}\n\n\t/**\n\t * Gets the template parameter extraction regex and mapping.\n\t *\n\t * @param logTemplate The log template\n\t * @returns Tuple of [templateRegex, paramGroupNameToMaskName]\n\t */\n\tprivate getTemplateParameterExtractionRegex(logTemplate: string): {\n\t\ttemplateRegex: string;\n\t\tparamGroupNameToMaskName: Map<string, string>;\n\t} {\n\t\tconst paramGroupNameToMaskName = new Map<string, string>();\n\t\tlet paramNameCounter = 0;\n\n\t\tconst getNextParamName = (): string => {\n\t\t\tconst paramGroupName = `p_${paramNameCounter}`;\n\t\t\tparamNameCounter++;\n\t\t\treturn paramGroupName;\n\t\t};\n\n\t\t// Create a named group with the respective patterns for the given mask-name\n\t\tconst createCaptureRegex = (maskName: string): string => {\n\t\t\tconst allowedPatterns: string[] = [];\n\n\t\t\tif (maskName === '*') {\n\t\t\t\tallowedPatterns.push('.+?');\n\t\t\t}\n\n\t\t\t// Give each capture group a unique name to avoid conflict\n\t\t\tconst paramGroupName = getNextParamName();\n\t\t\tparamGroupNameToMaskName.set(paramGroupName, maskName);\n\t\t\tconst joinedPatterns = allowedPatterns.join('|');\n\t\t\tconst captureRegex = `(?<${paramGroupName}>${joinedPatterns})`;\n\n\t\t\treturn captureRegex;\n\t\t};\n\n\t\t// For every mask in the template, replace it with a named group\n\t\tconst maskNames = new Set<string>();\n\t\t// The drain catch-all mask\n\t\tmaskNames.add('*');\n\n\t\t// Escape the template for regex\n\t\tlet templateRegex = this.escapeRegex(logTemplate);\n\n\t\t// Replace each mask name with a proper regex that captures it\n\t\tfor (const maskName of maskNames) {\n\t\t\tconst searchStr = `<${this.escapeRegex(maskName)}>`;\n\n\t\t\t// Replace one-by-one to get a new param group name for each replacement\n\t\t\twhile (true) {\n\t\t\t\tconst repStr = createCaptureRegex(maskName);\n\t\t\t\tconst templateRegexNew = templateRegex.replace(searchStr, repStr);\n\t\t\t\t// Break when all replaces for this mask are done\n\t\t\t\tif (templateRegexNew === templateRegex) {\n\t\t\t\t\tbreak;\n\t\t\t\t}\n\t\t\t\ttemplateRegex = templateRegexNew;\n\t\t\t}\n\t\t}\n\n\t\t// Match also messages with multiple spaces or other whitespace chars between tokens\n\t\ttemplateRegex = templateRegex.replace(/\\\\ /g, '\\\\s+');\n\t\ttemplateRegex = `^${templateRegex}$`;\n\n\t\treturn { templateRegex, paramGroupNameToMaskName };\n\t}\n\n\t/**\n\t * Escapes special regex characters in a string.\n\t */\n\tprivate escapeRegex(str: string): string {\n\t\treturn str.replace(/[.*+?^${}()|[\\]\\\\]/g, '\\\\$&');\n\t}\n\n\t/**\n\t * Loads the Drain state from persistence.\n\t *\n\t * @throws Error if loading fails\n\t */\n\tasync loadState(): Promise<void> {\n\t\tconst state = await this.persistence.load();\n\t\tif (state === null || (typeof state === 'string' && state.length === 0)) {\n\t\t\tthrow new Error('saved state not found');\n\t\t}\n\n\t\ttry {\n\t\t\tconst stateStr = typeof state === 'string' ? state : new TextDecoder().decode(state);\n\t\t\tconst data = JSON.parse(stateStr);\n\t\t\tthis.drain = Drain.fromJSON(data);\n\t\t} catch (error) {\n\t\t\tthrow new Error(`failed to unmarshal state: ${error instanceof Error ? error.message : String(error)}`);\n\t\t}\n\t}\n\n\t/**\n\t * Saves the Drain state to persistence.\n\t *\n\t * @throws Error if saving fails\n\t */\n\tasync saveState(): Promise<void> {\n\t\ttry {\n\t\t\tconst state = JSON.stringify(this.drain.toJSON());\n\t\t\tawait this.persistence.save(state);\n\t\t} catch (error) {\n\t\t\tthrow new Error(`failed to save state: ${error instanceof Error ? error.message : String(error)}`);\n\t\t}\n\t}\n\n\t/**\n\t * Gets the underlying Drain instance.\n\t */\n\tgetDrain(): Drain {\n\t\treturn this.drain;\n\t}\n}\n"],"names":["Drain","TemplateMiner","drain","persistence","addLogMessage","content","cluster","updateType","templateMined","getTemplate","clusterCount","getClusters","length","saveState","match","strategy","extractParameters","logTemplate","logMessage","processedMessage","delimiter","extraDelimiters","escapedDelimiter","replace","RegExp","templateRegex","paramGroupNameToMaskName","getTemplateParameterExtractionRegex","regex","extractedParameters","groups","groupName","value","Object","entries","undefined","maskName","get","push","execResult","exec","indices","Array","from","_","i","paramIndex","index","Map","paramNameCounter","getNextParamName","paramGroupName","createCaptureRegex","allowedPatterns","set","joinedPatterns","join","captureRegex","maskNames","Set","add","escapeRegex","searchStr","repStr","templateRegexNew","str","loadState","state","load","Error","stateStr","TextDecoder","decode","data","JSON","parse","fromJSON","error","message","String","stringify","toJSON","save","getDrain"],"mappings":"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"}
|
|
@@ -0,0 +1,31 @@
|
|
|
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
|
+
*/ export { Drain } from "./Drain.js";
|
|
27
|
+
export { LogCluster } from "./LogCluster.js";
|
|
28
|
+
export { Node } from "./Node.js";
|
|
29
|
+
export { TemplateMiner } from "./TemplateMiner.js";
|
|
30
|
+
export { MemoryPersistence } from "./persistence/MemoryPersistence.js";
|
|
31
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../src/drain3/index.ts"],"sourcesContent":["/**\n * Drain3 - A TypeScript implementation of the Drain log clustering algorithm.\n *\n * Drain is an online log parsing algorithm that groups log messages into clusters\n * based on their structural similarity, extracting templates by parameterizing\n * variable parts of the logs.\n *\n * @example\n * ```typescript\n * import { Drain, TemplateMiner, MemoryPersistence } from '@wener/common/drain3';\n *\n * // Basic usage\n * const drain = new Drain({\n * logClusterDepth: 4,\n * simTh: 0.4,\n * maxClusters: 1000\n * });\n *\n * const result = drain.addLogMessage('[INFO] User 123 logged in');\n * console.log(result.cluster.getTemplate()); // [INFO] User <*> logged in\n *\n * // Advanced usage with persistence\n * const miner = new TemplateMiner(drain, new MemoryPersistence());\n * await miner.addLogMessage('[INFO] User 123 logged in');\n * ```\n */\n\nexport { Drain } from './Drain';\nexport { LogCluster } from './LogCluster';\nexport { Node } from './Node';\nexport { TemplateMiner } from './TemplateMiner';\n\nexport type { ClusterUpdateType, DrainOptions, ExtractedParameter, SearchStrategy } from './types';\n\nexport type { PersistenceHandler } from './persistence/PersistenceHandler';\nexport { MemoryPersistence } from './persistence/MemoryPersistence';\n"],"names":["Drain","LogCluster","Node","TemplateMiner","MemoryPersistence"],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;CAyBC,GAED,SAASA,KAAK,QAAQ,UAAU;AAChC,SAASC,UAAU,QAAQ,eAAe;AAC1C,SAASC,IAAI,QAAQ,SAAS;AAC9B,SAASC,aAAa,QAAQ,kBAAkB;AAKhD,SAASC,iBAAiB,QAAQ,kCAAkC"}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import { readFile, writeFile } from "node:fs/promises";
|
|
2
|
+
/**
|
|
3
|
+
* File-based persistence handler that saves and loads state from the filesystem.
|
|
4
|
+
*/ export class FilePersistence {
|
|
5
|
+
filePath;
|
|
6
|
+
constructor(filePath){
|
|
7
|
+
this.filePath = filePath;
|
|
8
|
+
}
|
|
9
|
+
async save(state) {
|
|
10
|
+
await writeFile(this.filePath, state, 'utf8');
|
|
11
|
+
}
|
|
12
|
+
async load() {
|
|
13
|
+
try {
|
|
14
|
+
return await readFile(this.filePath, 'utf8');
|
|
15
|
+
} catch (error) {
|
|
16
|
+
if (error?.code === 'ENOENT') {
|
|
17
|
+
return null;
|
|
18
|
+
}
|
|
19
|
+
throw error;
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
//# sourceMappingURL=FilePersistence.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../../src/drain3/persistence/FilePersistence.ts"],"sourcesContent":["import { readFile, writeFile } from 'node:fs/promises';\nimport type { PersistenceHandler } from './PersistenceHandler';\n\n/**\n * File-based persistence handler that saves and loads state from the filesystem.\n */\nexport class FilePersistence implements PersistenceHandler {\n\tconstructor(private readonly filePath: string) {}\n\n\tasync save(state: Uint8Array | string): Promise<void> {\n\t\tawait writeFile(this.filePath, state, 'utf8');\n\t}\n\n\tasync load(): Promise<string | null> {\n\t\ttry {\n\t\t\treturn await readFile(this.filePath, 'utf8');\n\t\t} catch (error) {\n\t\t\tif ((error as { code?: string })?.code === 'ENOENT') {\n\t\t\t\treturn null;\n\t\t\t}\n\t\t\tthrow error;\n\t\t}\n\t}\n}\n"],"names":["readFile","writeFile","FilePersistence","filePath","save","state","load","error","code"],"mappings":"AAAA,SAASA,QAAQ,EAAEC,SAAS,QAAQ,mBAAmB;AAGvD;;CAEC,GACD,OAAO,MAAMC;;IACZ,YAAY,AAAiBC,QAAgB,CAAE;aAAlBA,WAAAA;IAAmB;IAEhD,MAAMC,KAAKC,KAA0B,EAAiB;QACrD,MAAMJ,UAAU,IAAI,CAACE,QAAQ,EAAEE,OAAO;IACvC;IAEA,MAAMC,OAA+B;QACpC,IAAI;YACH,OAAO,MAAMN,SAAS,IAAI,CAACG,QAAQ,EAAE;QACtC,EAAE,OAAOI,OAAO;YACf,IAAI,AAACA,OAA6BC,SAAS,UAAU;gBACpD,OAAO;YACR;YACA,MAAMD;QACP;IACD;AACD"}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* In-memory persistence handler that stores state in memory.
|
|
3
|
+
* Useful for testing or when persistence is not needed.
|
|
4
|
+
*/ export class MemoryPersistence {
|
|
5
|
+
state = null;
|
|
6
|
+
/**
|
|
7
|
+
* Saves the state to memory.
|
|
8
|
+
*/ async save(state) {
|
|
9
|
+
this.state = state;
|
|
10
|
+
}
|
|
11
|
+
/**
|
|
12
|
+
* Loads the state from memory.
|
|
13
|
+
*/ async load() {
|
|
14
|
+
return this.state;
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
//# sourceMappingURL=MemoryPersistence.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../../src/drain3/persistence/MemoryPersistence.ts"],"sourcesContent":["import type { PersistenceHandler } from './PersistenceHandler';\n\n/**\n * In-memory persistence handler that stores state in memory.\n * Useful for testing or when persistence is not needed.\n */\nexport class MemoryPersistence implements PersistenceHandler {\n\tprivate state: Uint8Array | string | null = null;\n\n\t/**\n\t * Saves the state to memory.\n\t */\n\tasync save(state: Uint8Array | string): Promise<void> {\n\t\tthis.state = state;\n\t}\n\n\t/**\n\t * Loads the state from memory.\n\t */\n\tasync load(): Promise<Uint8Array | string | null> {\n\t\treturn this.state;\n\t}\n}\n"],"names":["MemoryPersistence","state","save","load"],"mappings":"AAEA;;;CAGC,GACD,OAAO,MAAMA;IACJC,QAAoC,KAAK;IAEjD;;EAEC,GACD,MAAMC,KAAKD,KAA0B,EAAiB;QACrD,IAAI,CAACA,KAAK,GAAGA;IACd;IAEA;;EAEC,GACD,MAAME,OAA4C;QACjD,OAAO,IAAI,CAACF,KAAK;IAClB;AACD"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../../src/drain3/persistence/PersistenceHandler.ts"],"sourcesContent":["/**\n * Interface for persistence handlers that can save and load Drain state.\n */\nexport interface PersistenceHandler {\n\t/**\n\t * Saves the Drain state.\n\t *\n\t * @param state Serialized state data\n\t * @returns Promise that resolves when save is complete\n\t */\n\tsave(state: Uint8Array | string): Promise<void>;\n\n\t/**\n\t * Loads the Drain state.\n\t *\n\t * @returns Promise that resolves with the loaded state, or null if no state exists\n\t */\n\tload(): Promise<Uint8Array | string | null>;\n}\n"],"names":[],"mappings":"AAAA;;CAEC,GACD,WAeC"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../src/drain3/types.ts"],"sourcesContent":["/**\n * Cluster update type indicating what happened when a log message was processed.\n */\nexport type ClusterUpdateType = 'none' | 'created' | 'templateChanged';\n\n/**\n * Search strategy for matching log messages against existing clusters.\n *\n * - 'never': Fastest, always performs tree search [O(log(n))] but might produce false negatives\n * - 'fallback': Performs linear search [O(n)] only if tree search found no match, should not have false negatives\n * - 'always': Slowest, always evaluates all clusters and selects the best match with least wildcard parameters\n */\nexport type SearchStrategy = 'never' | 'fallback' | 'always';\n\n/**\n * Configuration options for Drain algorithm.\n */\nexport interface DrainOptions {\n\t/**\n\t * Depth of the prefix tree for log clustering. Must be at least 3.\n\t * @default 4\n\t */\n\tlogClusterDepth?: number;\n\n\t/**\n\t * Similarity threshold (0.0 to 1.0) for matching log messages to clusters.\n\t * @default 0.4\n\t */\n\tsimTh?: number;\n\n\t/**\n\t * Maximum number of children nodes in the prefix tree.\n\t * @default 100\n\t */\n\tmaxChildren?: number;\n\n\t/**\n\t * Maximum number of clusters to maintain in LRU cache.\n\t * @default 1000\n\t */\n\tmaxClusters?: number;\n\n\t/**\n\t * Additional delimiters to replace with spaces during tokenization.\n\t * @default []\n\t */\n\textraDelimiters?: string[];\n\n\t/**\n\t * String used to represent parameterized tokens in templates.\n\t * @default \"<*>\"\n\t */\n\tparamStr?: string;\n\n\t/**\n\t * Whether to automatically parameterize tokens containing numbers.\n\t * @default true\n\t */\n\tparametrizeNumericTokens?: boolean;\n}\n\n/**\n * Extracted parameter from a log message based on a template.\n */\nexport interface ExtractedParameter {\n\t/**\n\t * The extracted parameter value.\n\t */\n\tvalue: string;\n\n\t/**\n\t * The mask name (e.g., \"*\") that matched this parameter.\n\t */\n\tmaskName: string;\n}\n"],"names":[],"mappings":"AAAA;;CAEC,GA2DD;;CAEC,GACD,WAUC"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../src/fs/IFileSystem.d.ts"],"sourcesContent":["import type { FileKind, FileUrlOptions } from './types';\n\n// Base operation options\ntype OperationOptions = {\n\tsignal?: AbortSignal;\n};\n\n// Directory operations\nexport type ReaddirOptions = OperationOptions & {\n\tglob?: string;\n\trecursive?: boolean;\n\tdepth?: number;\n\tkind?: FileKind;\n\thidden?: boolean;\n};\nexport type MkdirOptions = OperationOptions & {\n\trecursive?: boolean;\n};\n// File operations\nexport type ReadFileOptions = OperationOptions & {\n\tencoding?: 'text' | 'binary';\n\tonDownloadProgress?: (e: { loaded: number; total: number }) => void;\n};\nexport type WriteFileOptions = OperationOptions & {\n\toverwrite?: boolean;\n\tonUploadProgress?: (e: { loaded: number; total: number }) => void;\n};\nexport type RenameOptions = OperationOptions & {\n\toverwrite?: boolean;\n};\nexport type RmOptions = OperationOptions & {\n\trecursive?: boolean;\n\tforce?: boolean;\n};\nexport type CopyOptions = OperationOptions & {\n\toverwrite?: boolean;\n\tshallow?: boolean;\n};\nexport type CreateReadStreamOptions = OperationOptions & {\n\trange?: { start: number; end?: number };\n\tsignal?: AbortSignal;\n};\nexport type CreateWriteStreamOptions = OperationOptions & {\n\toverwrite?: boolean;\n};\nexport type StatOptions = OperationOptions & {};\n\ntype WritableData = string | ArrayBuffer | ArrayBufferView | ReadableStream;\n\n/**\n * Universal file system interface (browser & server compatible)\n */\nexport type IFileSystem = {\n\treaddir(dir: string, options?: ReaddirOptions): Promise<IFileStat[]>;\n\tstat(entry: string, options?: StatOptions): Promise<IFileStat>;\n\tmkdir(path: string, options?: MkdirOptions): Promise<void>;\n\treadFile(path: string, options?: ReadFileOptions & { encoding: 'text' }): Promise<string>;\n\treadFile(path: string, options?: ReadFileOptions): Promise<Uint8Array>;\n\twriteFile(path: string, data: WritableData, options?: WriteFileOptions): Promise<void>;\n\trm(path: string, options?: RmOptions): Promise<void>;\n\trename(oldPath: string, newPath: string, options?: RenameOptions): Promise<void>;\n\texists(path: string): Promise<boolean>;\n\tcopy(src: string, dest: string, options?: CopyOptions): Promise<void>;\n\n\tgetUrl?(path: IFileStat | string, options?: FileUrlOptions): string | undefined;\n\n\tcreateReadableStream?(path: string, options?: CreateReadStreamOptions): ReadableStream;\n\tcreateWritableStream?(path: string, options?: CreateWriteStreamOptions): WritableStream;\n};\n\n/**\n * Server/Node.js specific file system interface with stream support\n */\nexport type IServerFileSystem = IFileSystem & {\n\tcreateReadStream(path: string, options?: CreateReadStreamOptions): import('node:stream').Readable;\n\tcreateWriteStream(path: string, options?: CreateWriteStreamOptions): import('node:stream').Writable;\n\twriteFile(\n\t\tpath: string,\n\t\tdata: WritableData | Buffer | import('node:stream').Readable,\n\t\toptions?: WriteFileOptions,\n\t): Promise<void>;\n};\n\nexport type IFileStat = {\n\t/**\n\t * parent path\n\t */\n\tdirectory: string;\n\t/**\n\t * full path\n\t */\n\tpath: string;\n\t/**\n\t * basename\n\t */\n\tname: string;\n\tkind: 'directory' | 'file';\n\tmtime: number;\n\tmeta: Record<string, any>;\n\tsize: number;\n};\n
|
|
1
|
+
{"version":3,"sources":["../../src/fs/IFileSystem.d.ts"],"sourcesContent":["import type { FileKind, FileUrlOptions } from './types';\n\n// Base operation options\ntype OperationOptions = {\n\tsignal?: AbortSignal;\n};\n\n// Directory operations\nexport type ReaddirOptions = OperationOptions & {\n\tglob?: string;\n\trecursive?: boolean;\n\tdepth?: number;\n\tkind?: FileKind;\n\thidden?: boolean;\n};\nexport type MkdirOptions = OperationOptions & {\n\trecursive?: boolean;\n};\n// File operations\nexport type ReadFileOptions = OperationOptions & {\n\tencoding?: 'text' | 'binary';\n\tonDownloadProgress?: (e: { loaded: number; total: number }) => void;\n};\nexport type WriteFileOptions = OperationOptions & {\n\toverwrite?: boolean;\n\tonUploadProgress?: (e: { loaded: number; total: number }) => void;\n};\nexport type RenameOptions = OperationOptions & {\n\toverwrite?: boolean;\n};\nexport type RmOptions = OperationOptions & {\n\trecursive?: boolean;\n\tforce?: boolean;\n};\nexport type CopyOptions = OperationOptions & {\n\toverwrite?: boolean;\n\tshallow?: boolean;\n};\nexport type CreateReadStreamOptions = OperationOptions & {\n\trange?: { start: number; end?: number };\n\tsignal?: AbortSignal;\n};\nexport type CreateWriteStreamOptions = OperationOptions & {\n\toverwrite?: boolean;\n};\nexport type StatOptions = OperationOptions & {};\n\ntype WritableData = string | ArrayBuffer | ArrayBufferView<ArrayBufferLike> | ReadableStream;\n\n/**\n * Universal file system interface (browser & server compatible)\n */\nexport type IFileSystem = {\n\treaddir(dir: string, options?: ReaddirOptions): Promise<IFileStat[]>;\n\tstat(entry: string, options?: StatOptions): Promise<IFileStat>;\n\tmkdir(path: string, options?: MkdirOptions): Promise<void>;\n\treadFile(path: string, options?: ReadFileOptions & { encoding: 'text' }): Promise<string>;\n\treadFile(path: string, options?: ReadFileOptions): Promise<Uint8Array>;\n\twriteFile(path: string, data: WritableData, options?: WriteFileOptions): Promise<void>;\n\trm(path: string, options?: RmOptions): Promise<void>;\n\trename(oldPath: string, newPath: string, options?: RenameOptions): Promise<void>;\n\texists(path: string): Promise<boolean>;\n\tcopy(src: string, dest: string, options?: CopyOptions): Promise<void>;\n\n\tgetUrl?(path: IFileStat | string, options?: FileUrlOptions): string | undefined;\n\n\tcreateReadableStream?(path: string, options?: CreateReadStreamOptions): ReadableStream;\n\tcreateWritableStream?(path: string, options?: CreateWriteStreamOptions): WritableStream;\n};\n\n/**\n * Server/Node.js specific file system interface with stream support\n */\nexport type IServerFileSystem = IFileSystem & {\n\tcreateReadStream(path: string, options?: CreateReadStreamOptions): import('node:stream').Readable;\n\tcreateWriteStream(path: string, options?: CreateWriteStreamOptions): import('node:stream').Writable;\n\twriteFile(\n\t\tpath: string,\n\t\tdata: WritableData | Buffer | import('node:stream').Readable,\n\t\toptions?: WriteFileOptions,\n\t): Promise<void>;\n};\n\nexport type IFileStat = {\n\t/**\n\t * parent path\n\t */\n\tdirectory: string;\n\t/**\n\t * full path\n\t */\n\tpath: string;\n\t/**\n\t * basename\n\t */\n\tname: string;\n\tkind: 'directory' | 'file';\n\tmtime: number;\n\tmeta: Record<string, any>;\n\tsize: number;\n};\n"],"names":[],"mappings":"AAmFA,WAiBE"}
|
|
@@ -107,7 +107,7 @@ let BrowserFS = class BrowserFS {
|
|
|
107
107
|
const [handle] = await this._getHandle(path);
|
|
108
108
|
return !!handle;
|
|
109
109
|
}
|
|
110
|
-
async readdir(dir,
|
|
110
|
+
async readdir(dir, _options) {
|
|
111
111
|
const [handle] = await this._getHandle(dir);
|
|
112
112
|
if (!handle) {
|
|
113
113
|
throw new BrowserFSError(`Directory not found: ${dir}`, 'ENOENT');
|
|
@@ -116,6 +116,8 @@ let BrowserFS = class BrowserFS {
|
|
|
116
116
|
throw new BrowserFSError(`Not a directory: ${dir}`, 'ENOTDIR');
|
|
117
117
|
}
|
|
118
118
|
const entries = [];
|
|
119
|
+
// FileSystemDirectoryHandle.values() is part of the File System Access API
|
|
120
|
+
// but not yet in TypeScript's lib.dom.d.ts
|
|
119
121
|
for await (const entry of handle.values()){
|
|
120
122
|
entries.push(await this._toFileStat(entry, join(dir, entry.name)));
|
|
121
123
|
}
|
|
@@ -221,7 +223,7 @@ let BrowserFS = class BrowserFS {
|
|
|
221
223
|
}
|
|
222
224
|
}
|
|
223
225
|
}
|
|
224
|
-
async createUrl(stat,
|
|
226
|
+
async createUrl(stat, _options) {
|
|
225
227
|
// The handle is stored in the meta property in our _toFileStat method
|
|
226
228
|
const handle = stat.meta.handle;
|
|
227
229
|
if (handle?.kind !== 'file') {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../src/fs/createBrowserFileSystem.ts"],"sourcesContent":["import { dirname, join, normalize } from 'pathe';\nimport type {\n\tCopyOptions,\n\tIFileStat,\n\tIFileSystem,\n\tMkdirOptions,\n\tReaddirOptions,\n\tReadFileOptions,\n\tRenameOptions,\n\tRmOptions,\n\tWriteFileOptions,\n} from './IFileSystem';\nimport type { FileUrlOptions } from './types';\n\nclass BrowserFSError extends Error {\n\tconstructor(\n\t\tmessage: string,\n\t\tpublic code?: string,\n\t) {\n\t\tsuper(message);\n\t\tthis.name = 'BrowserFSError';\n\t}\n}\n\ntype BrowserFile = IFileStat & {\n\tkind: 'file';\n\tmeta: {\n\t\thandle: FileSystemFileHandle;\n\t\tfile: File;\n\t};\n};\n\ntype BrowserDirectory = IFileStat & {\n\tkind: 'directory';\n\tmeta: {\n\t\thandle: FileSystemDirectoryHandle;\n\t};\n};\n\ntype BrowserNode = BrowserFile | BrowserDirectory;\n\n/**\n * Creates an IFileSystem instance backed by the browser's File System Access API.\n * @param root A FileSystemDirectoryHandle, usually obtained from `window.showDirectoryPicker()`.\n */\nexport function createBrowserFileSystem(root: FileSystemDirectoryHandle): IFileSystem {\n\treturn new BrowserFS(root);\n}\n\nclass BrowserFS implements IFileSystem {\n\troot: FileSystemDirectoryHandle;\n\tnode: BrowserDirectory;\n\n\tconstructor(root: FileSystemDirectoryHandle) {\n\t\tthis.root = root;\n\t\tthis.node = {\n\t\t\tpath: '/',\n\t\t\tname: '',\n\t\t\tdirectory: '',\n\t\t\tkind: 'directory',\n\t\t\tmtime: 0,\n\t\t\tsize: 0,\n\t\t\tmeta: {\n\t\t\t\thandle: root,\n\t\t\t},\n\t\t};\n\t}\n\n\t/**\n\t * Resolves a path string to a file or directory handle. This is the core navigation logic.\n\t * @returns A tuple of [handle, parentHandle, name] or [null, parentHandle, name] if not found.\n\t */\n\tprivate async _getHandle(path: string): Promise<[FileSystemHandle | null, FileSystemDirectoryHandle | null, string]> {\n\t\tconst normalized = normalize(path);\n\t\tif (normalized === '/' || normalized === '.') {\n\t\t\treturn [this.root, null, this.root.name];\n\t\t}\n\n\t\tconst parts = normalized.split('/').filter((p) => p);\n\t\tconst name = parts.pop()!;\n\n\t\tlet parent: FileSystemDirectoryHandle = this.root;\n\t\tfor (const part of parts) {\n\t\t\ttry {\n\t\t\t\tparent = await parent.getDirectoryHandle(part);\n\t\t\t} catch (e: any) {\n\t\t\t\tif (e.name === 'NotFoundError' || e.name === 'TypeMismatchError') {\n\t\t\t\t\treturn [null, null, name]; // Parent path does not exist or is a file\n\t\t\t\t}\n\t\t\t\tthrow e;\n\t\t\t}\n\t\t}\n\n\t\ttry {\n\t\t\t// Check for file first, then directory\n\t\t\tconst handle = await parent.getFileHandle(name).catch(() => parent.getDirectoryHandle(name));\n\t\t\treturn [handle, parent, name];\n\t\t} catch (e: any) {\n\t\t\tif (e.name === 'NotFoundError') {\n\t\t\t\treturn [null, parent, name]; // Entry not found\n\t\t\t}\n\t\t\tthrow e;\n\t\t}\n\t}\n\n\t/**\n\t * Converts a FileSystemHandle into our standard IFileStat object.\n\t */\n\tprivate async _toFileStat(handle: FileSystemHandle, path: string): Promise<IFileStat> {\n\t\tconst isFile = handle.kind === 'file';\n\t\tconst file = isFile ? await (handle as FileSystemFileHandle).getFile() : undefined;\n\n\t\treturn {\n\t\t\tname: handle.name,\n\t\t\tkind: handle.kind,\n\t\t\tpath: path,\n\t\t\tdirectory: dirname(path),\n\t\t\tsize: file?.size ?? 0,\n\t\t\tmtime: file?.lastModified ?? Date.now(),\n\t\t\tmeta: { handle, file },\n\t\t};\n\t}\n\n\t// --- IFileSystem Implementation ---\n\n\tasync stat(path: string): Promise<IFileStat> {\n\t\tconst [handle] = await this._getHandle(path);\n\t\tif (!handle) {\n\t\t\tthrow new BrowserFSError(`Path not found: ${path}`, 'ENOENT');\n\t\t}\n\t\treturn this._toFileStat(handle, path);\n\t}\n\n\tasync exists(path: string): Promise<boolean> {\n\t\tconst [handle] = await this._getHandle(path);\n\t\treturn !!handle;\n\t}\n\n\tasync readdir(dir: string, options?: ReaddirOptions): Promise<IFileStat[]> {\n\t\tconst [handle] = await this._getHandle(dir);\n\t\tif (!handle) {\n\t\t\tthrow new BrowserFSError(`Directory not found: ${dir}`, 'ENOENT');\n\t\t}\n\t\tif (handle.kind !== 'directory') {\n\t\t\tthrow new BrowserFSError(`Not a directory: ${dir}`, 'ENOTDIR');\n\t\t}\n\n\t\tconst entries: IFileStat[] = [];\n\t\tfor await (const entry of (handle as FileSystemDirectoryHandle).values()) {\n\t\t\tentries.push(await this._toFileStat(entry, join(dir, entry.name)));\n\t\t}\n\t\treturn entries;\n\t}\n\n\tasync mkdir(path: string, options: MkdirOptions = {}): Promise<void> {\n\t\tconst parts = normalize(path)\n\t\t\t.split('/')\n\t\t\t.filter((p) => p);\n\t\tlet currentDir = this.root;\n\n\t\tfor (const part of parts) {\n\t\t\ttry {\n\t\t\t\tcurrentDir = await currentDir.getDirectoryHandle(part, { create: options.recursive });\n\t\t\t} catch (e: any) {\n\t\t\t\t// If recursive is false, getDirectoryHandle throws if a segment is missing.\n\t\t\t\t// We re-throw a more standard error.\n\t\t\t\tif (e.name === 'NotFoundError' && !options.recursive) {\n\t\t\t\t\tthrow new BrowserFSError(`Cannot create directory: Parent does not exist for path ${path}`, 'ENOENT');\n\t\t\t\t}\n\t\t\t\t// If it's another error (like a file with the same name), re-throw it.\n\t\t\t\tthrow e;\n\t\t\t}\n\t\t}\n\t}\n\n\treadFile(path: string, options?: ReadFileOptions & { encoding: 'text' }): Promise<string>;\n\treadFile(path: string, options?: ReadFileOptions): Promise<Uint8Array>;\n\tasync readFile(path: string, options?: ReadFileOptions): Promise<string | Uint8Array> {\n\t\tconst [handle] = await this._getHandle(path);\n\t\tif (!handle || handle.kind !== 'file') {\n\t\t\tthrow new BrowserFSError(`File not found: ${path}`, 'ENOENT');\n\t\t}\n\t\tconst file = await (handle as FileSystemFileHandle).getFile();\n\t\treturn options?.encoding === 'text' ? file.text() : file.bytes();\n\t}\n\n\tasync writeFile(path: string, data: any, options: WriteFileOptions = {}): Promise<void> {\n\t\tconst { overwrite = true } = options;\n\t\tconst [handle, parent, name] = await this._getHandle(path);\n\n\t\tif (!parent) {\n\t\t\tthrow new BrowserFSError(`Parent directory does not exist for path: ${path}`, 'ENOENT');\n\t\t}\n\t\tif (handle && !overwrite) {\n\t\t\tthrow new BrowserFSError(`File already exists: ${path}`, 'EEXIST');\n\t\t}\n\n\t\tconst fileHandle = await parent.getFileHandle(name, { create: true });\n\t\tconst writable = await fileHandle.createWritable();\n\t\tawait writable.write(data);\n\t\tawait writable.close();\n\t}\n\n\tasync rm(path: string, options: RmOptions = {}): Promise<void> {\n\t\tconst { recursive = false, force = false } = options;\n\t\tconst [handle, parent, name] = await this._getHandle(path);\n\n\t\tif (!handle) {\n\t\t\tif (force) return; // If force is true, do not error on not found.\n\t\t\tthrow new BrowserFSError(`Path not found: ${path}`, 'ENOENT');\n\t\t}\n\t\tif (!parent) {\n\t\t\tthrow new BrowserFSError(`Cannot remove root directory`);\n\t\t}\n\n\t\tawait parent.removeEntry(name, { recursive });\n\t}\n\n\tasync rename(oldPath: string, newPath: string, options?: RenameOptions): Promise<void> {\n\t\t// The File System Access API does NOT have a native move/rename.\n\t\t// The standard workaround is to copy, then delete the original.\n\t\tawait this.copy(oldPath, newPath, options);\n\t\tawait this.rm(oldPath, { recursive: true });\n\t}\n\n\tasync copy(srcPath: string, destPath: string, options?: CopyOptions): Promise<void> {\n\t\tconst [srcHandle] = await this._getHandle(srcPath);\n\t\tif (!srcHandle) {\n\t\t\tthrow new BrowserFSError(`Source not found: ${srcPath}`, 'ENOENT');\n\t\t}\n\n\t\tconst [destHandle, destParent, destName] = await this._getHandle(destPath);\n\t\tif (!destParent) {\n\t\t\tthrow new BrowserFSError(`Destination directory does not exist: ${dirname(destPath)}`, 'ENOENT');\n\t\t}\n\t\tif (destHandle && !options?.overwrite) {\n\t\t\tthrow new BrowserFSError(`Destination already exists: ${destPath}`, 'EEXIST');\n\t\t}\n\t\tif (destHandle?.kind === 'directory' && srcHandle.kind === 'file') {\n\t\t\tthrow new BrowserFSError(`Cannot overwrite a directory with a file: ${destPath}`, 'EISDIR');\n\t\t}\n\n\t\tawait this._copyEntry(srcHandle, destParent, destName);\n\t}\n\n\tprivate async _copyEntry(\n\t\tsrcHandle: FileSystemHandle,\n\t\tdestDirHandle: FileSystemDirectoryHandle,\n\t\tnewName: string,\n\t): Promise<void> {\n\t\tif (srcHandle.kind === 'file') {\n\t\t\tconst file = await (srcHandle as FileSystemFileHandle).getFile();\n\t\t\tconst destFileHandle = await destDirHandle.getFileHandle(newName, { create: true });\n\t\t\tconst writable = await destFileHandle.createWritable();\n\t\t\tawait writable.write(file);\n\t\t\tawait writable.close();\n\t\t} else if (srcHandle.kind === 'directory') {\n\t\t\tconst newDirHandle = await destDirHandle.getDirectoryHandle(newName, { create: true });\n\t\t\tfor await (const entry of (srcHandle as FileSystemDirectoryHandle).values()) {\n\t\t\t\tawait this._copyEntry(entry, newDirHandle, entry.name);\n\t\t\t}\n\t\t}\n\t}\n\n\tasync createUrl(stat: IFileStat, options?: FileUrlOptions): Promise<string> {\n\t\t// The handle is stored in the meta property in our _toFileStat method\n\t\tconst handle = stat.meta.handle as FileSystemHandle | undefined;\n\t\tif (handle?.kind !== 'file') {\n\t\t\t// Return a placeholder or throw an error for directories\n\t\t\treturn '';\n\t\t}\n\t\tconst file = await (handle as FileSystemFileHandle).getFile();\n\t\treturn URL.createObjectURL(file);\n\t}\n\n\tcreateReadStream(): never {\n\t\tthrow new Error('Streaming is not implemented for BrowserFS. Use readFile instead.');\n\t}\n\n\tcreateWriteStream(): never {\n\t\tthrow new Error('Streaming is not implemented for BrowserFS. Use writeFile instead.');\n\t}\n\n\tcreateWritableStream(): never {\n\t\tthrow new Error('Streaming is not implemented for BrowserFS. Use writeFile instead.');\n\t}\n\n\tcreateReadableStream(): never {\n\t\tthrow new Error('Streaming is not implemented for BrowserFS. Use readFile instead.');\n\t}\n}\n"],"names":["dirname","join","normalize","BrowserFSError","Error","message","code","name","createBrowserFileSystem","root","BrowserFS","node","path","directory","kind","mtime","size","meta","handle","_getHandle","normalized","parts","split","filter","p","pop","parent","part","getDirectoryHandle","e","getFileHandle","catch","_toFileStat","isFile","file","getFile","undefined","lastModified","Date","now","stat","exists","readdir","dir","options","entries","entry","values","push","mkdir","currentDir","create","recursive","readFile","encoding","text","bytes","writeFile","data","overwrite","fileHandle","writable","createWritable","write","close","rm","force","removeEntry","rename","oldPath","newPath","copy","srcPath","destPath","srcHandle","destHandle","destParent","destName","_copyEntry","destDirHandle","newName","destFileHandle","newDirHandle","createUrl","URL","createObjectURL","createReadStream","createWriteStream","createWritableStream","createReadableStream"],"mappings":"AAAA,SAASA,OAAO,EAAEC,IAAI,EAAEC,SAAS,QAAQ,QAAQ;AAcjD,IAAA,AAAMC,iBAAN,MAAMA,uBAAuBC;;IAC5B,YACCC,OAAe,EACf,AAAOC,IAAa,CACnB;QACD,KAAK,CAACD,eAFCC,OAAAA;QAGP,IAAI,CAACC,IAAI,GAAG;IACb;AACD;AAmBA;;;CAGC,GACD,OAAO,SAASC,wBAAwBC,IAA+B;IACtE,OAAO,IAAIC,UAAUD;AACtB;AAEA,IAAA,AAAMC,YAAN,MAAMA;IACLD,KAAgC;IAChCE,KAAuB;IAEvB,YAAYF,IAA+B,CAAE;QAC5C,IAAI,CAACA,IAAI,GAAGA;QACZ,IAAI,CAACE,IAAI,GAAG;YACXC,MAAM;YACNL,MAAM;YACNM,WAAW;YACXC,MAAM;YACNC,OAAO;YACPC,MAAM;YACNC,MAAM;gBACLC,QAAQT;YACT;QACD;IACD;IAEA;;;EAGC,GACD,MAAcU,WAAWP,IAAY,EAAgF;QACpH,MAAMQ,aAAalB,UAAUU;QAC7B,IAAIQ,eAAe,OAAOA,eAAe,KAAK;YAC7C,OAAO;gBAAC,IAAI,CAACX,IAAI;gBAAE;gBAAM,IAAI,CAACA,IAAI,CAACF,IAAI;aAAC;QACzC;QAEA,MAAMc,QAAQD,WAAWE,KAAK,CAAC,KAAKC,MAAM,CAAC,CAACC,IAAMA;QAClD,MAAMjB,OAAOc,MAAMI,GAAG;QAEtB,IAAIC,SAAoC,IAAI,CAACjB,IAAI;QACjD,KAAK,MAAMkB,QAAQN,MAAO;YACzB,IAAI;gBACHK,SAAS,MAAMA,OAAOE,kBAAkB,CAACD;YAC1C,EAAE,OAAOE,GAAQ;gBAChB,IAAIA,EAAEtB,IAAI,KAAK,mBAAmBsB,EAAEtB,IAAI,KAAK,qBAAqB;oBACjE,OAAO;wBAAC;wBAAM;wBAAMA;qBAAK,EAAE,0CAA0C;gBACtE;gBACA,MAAMsB;YACP;QACD;QAEA,IAAI;YACH,uCAAuC;YACvC,MAAMX,SAAS,MAAMQ,OAAOI,aAAa,CAACvB,MAAMwB,KAAK,CAAC,IAAML,OAAOE,kBAAkB,CAACrB;YACtF,OAAO;gBAACW;gBAAQQ;gBAAQnB;aAAK;QAC9B,EAAE,OAAOsB,GAAQ;YAChB,IAAIA,EAAEtB,IAAI,KAAK,iBAAiB;gBAC/B,OAAO;oBAAC;oBAAMmB;oBAAQnB;iBAAK,EAAE,kBAAkB;YAChD;YACA,MAAMsB;QACP;IACD;IAEA;;EAEC,GACD,MAAcG,YAAYd,MAAwB,EAAEN,IAAY,EAAsB;QACrF,MAAMqB,SAASf,OAAOJ,IAAI,KAAK;QAC/B,MAAMoB,OAAOD,SAAS,MAAM,AAACf,OAAgCiB,OAAO,KAAKC;QAEzE,OAAO;YACN7B,MAAMW,OAAOX,IAAI;YACjBO,MAAMI,OAAOJ,IAAI;YACjBF,MAAMA;YACNC,WAAWb,QAAQY;YACnBI,MAAMkB,MAAMlB,QAAQ;YACpBD,OAAOmB,MAAMG,gBAAgBC,KAAKC,GAAG;YACrCtB,MAAM;gBAAEC;gBAAQgB;YAAK;QACtB;IACD;IAEA,qCAAqC;IAErC,MAAMM,KAAK5B,IAAY,EAAsB;QAC5C,MAAM,CAACM,OAAO,GAAG,MAAM,IAAI,CAACC,UAAU,CAACP;QACvC,IAAI,CAACM,QAAQ;YACZ,MAAM,IAAIf,eAAe,CAAC,gBAAgB,EAAES,MAAM,EAAE;QACrD;QACA,OAAO,IAAI,CAACoB,WAAW,CAACd,QAAQN;IACjC;IAEA,MAAM6B,OAAO7B,IAAY,EAAoB;QAC5C,MAAM,CAACM,OAAO,GAAG,MAAM,IAAI,CAACC,UAAU,CAACP;QACvC,OAAO,CAAC,CAACM;IACV;IAEA,MAAMwB,QAAQC,GAAW,EAAEC,OAAwB,EAAwB;QAC1E,MAAM,CAAC1B,OAAO,GAAG,MAAM,IAAI,CAACC,UAAU,CAACwB;QACvC,IAAI,CAACzB,QAAQ;YACZ,MAAM,IAAIf,eAAe,CAAC,qBAAqB,EAAEwC,KAAK,EAAE;QACzD;QACA,IAAIzB,OAAOJ,IAAI,KAAK,aAAa;YAChC,MAAM,IAAIX,eAAe,CAAC,iBAAiB,EAAEwC,KAAK,EAAE;QACrD;QAEA,MAAME,UAAuB,EAAE;QAC/B,WAAW,MAAMC,SAAS,AAAC5B,OAAqC6B,MAAM,GAAI;YACzEF,QAAQG,IAAI,CAAC,MAAM,IAAI,CAAChB,WAAW,CAACc,OAAO7C,KAAK0C,KAAKG,MAAMvC,IAAI;QAChE;QACA,OAAOsC;IACR;IAEA,MAAMI,MAAMrC,IAAY,EAAEgC,UAAwB,CAAC,CAAC,EAAiB;QACpE,MAAMvB,QAAQnB,UAAUU,MACtBU,KAAK,CAAC,KACNC,MAAM,CAAC,CAACC,IAAMA;QAChB,IAAI0B,aAAa,IAAI,CAACzC,IAAI;QAE1B,KAAK,MAAMkB,QAAQN,MAAO;YACzB,IAAI;gBACH6B,aAAa,MAAMA,WAAWtB,kBAAkB,CAACD,MAAM;oBAAEwB,QAAQP,QAAQQ,SAAS;gBAAC;YACpF,EAAE,OAAOvB,GAAQ;gBAChB,4EAA4E;gBAC5E,qCAAqC;gBACrC,IAAIA,EAAEtB,IAAI,KAAK,mBAAmB,CAACqC,QAAQQ,SAAS,EAAE;oBACrD,MAAM,IAAIjD,eAAe,CAAC,wDAAwD,EAAES,MAAM,EAAE;gBAC7F;gBACA,uEAAuE;gBACvE,MAAMiB;YACP;QACD;IACD;IAIA,MAAMwB,SAASzC,IAAY,EAAEgC,OAAyB,EAAgC;QACrF,MAAM,CAAC1B,OAAO,GAAG,MAAM,IAAI,CAACC,UAAU,CAACP;QACvC,IAAI,CAACM,UAAUA,OAAOJ,IAAI,KAAK,QAAQ;YACtC,MAAM,IAAIX,eAAe,CAAC,gBAAgB,EAAES,MAAM,EAAE;QACrD;QACA,MAAMsB,OAAO,MAAM,AAAChB,OAAgCiB,OAAO;QAC3D,OAAOS,SAASU,aAAa,SAASpB,KAAKqB,IAAI,KAAKrB,KAAKsB,KAAK;IAC/D;IAEA,MAAMC,UAAU7C,IAAY,EAAE8C,IAAS,EAAEd,UAA4B,CAAC,CAAC,EAAiB;QACvF,MAAM,EAAEe,YAAY,IAAI,EAAE,GAAGf;QAC7B,MAAM,CAAC1B,QAAQQ,QAAQnB,KAAK,GAAG,MAAM,IAAI,CAACY,UAAU,CAACP;QAErD,IAAI,CAACc,QAAQ;YACZ,MAAM,IAAIvB,eAAe,CAAC,0CAA0C,EAAES,MAAM,EAAE;QAC/E;QACA,IAAIM,UAAU,CAACyC,WAAW;YACzB,MAAM,IAAIxD,eAAe,CAAC,qBAAqB,EAAES,MAAM,EAAE;QAC1D;QAEA,MAAMgD,aAAa,MAAMlC,OAAOI,aAAa,CAACvB,MAAM;YAAE4C,QAAQ;QAAK;QACnE,MAAMU,WAAW,MAAMD,WAAWE,cAAc;QAChD,MAAMD,SAASE,KAAK,CAACL;QACrB,MAAMG,SAASG,KAAK;IACrB;IAEA,MAAMC,GAAGrD,IAAY,EAAEgC,UAAqB,CAAC,CAAC,EAAiB;QAC9D,MAAM,EAAEQ,YAAY,KAAK,EAAEc,QAAQ,KAAK,EAAE,GAAGtB;QAC7C,MAAM,CAAC1B,QAAQQ,QAAQnB,KAAK,GAAG,MAAM,IAAI,CAACY,UAAU,CAACP;QAErD,IAAI,CAACM,QAAQ;YACZ,IAAIgD,OAAO,QAAQ,+CAA+C;YAClE,MAAM,IAAI/D,eAAe,CAAC,gBAAgB,EAAES,MAAM,EAAE;QACrD;QACA,IAAI,CAACc,QAAQ;YACZ,MAAM,IAAIvB,eAAe,CAAC,4BAA4B,CAAC;QACxD;QAEA,MAAMuB,OAAOyC,WAAW,CAAC5D,MAAM;YAAE6C;QAAU;IAC5C;IAEA,MAAMgB,OAAOC,OAAe,EAAEC,OAAe,EAAE1B,OAAuB,EAAiB;QACtF,iEAAiE;QACjE,gEAAgE;QAChE,MAAM,IAAI,CAAC2B,IAAI,CAACF,SAASC,SAAS1B;QAClC,MAAM,IAAI,CAACqB,EAAE,CAACI,SAAS;YAAEjB,WAAW;QAAK;IAC1C;IAEA,MAAMmB,KAAKC,OAAe,EAAEC,QAAgB,EAAE7B,OAAqB,EAAiB;QACnF,MAAM,CAAC8B,UAAU,GAAG,MAAM,IAAI,CAACvD,UAAU,CAACqD;QAC1C,IAAI,CAACE,WAAW;YACf,MAAM,IAAIvE,eAAe,CAAC,kBAAkB,EAAEqE,SAAS,EAAE;QAC1D;QAEA,MAAM,CAACG,YAAYC,YAAYC,SAAS,GAAG,MAAM,IAAI,CAAC1D,UAAU,CAACsD;QACjE,IAAI,CAACG,YAAY;YAChB,MAAM,IAAIzE,eAAe,CAAC,sCAAsC,EAAEH,QAAQyE,WAAW,EAAE;QACxF;QACA,IAAIE,cAAc,CAAC/B,SAASe,WAAW;YACtC,MAAM,IAAIxD,eAAe,CAAC,4BAA4B,EAAEsE,UAAU,EAAE;QACrE;QACA,IAAIE,YAAY7D,SAAS,eAAe4D,UAAU5D,IAAI,KAAK,QAAQ;YAClE,MAAM,IAAIX,eAAe,CAAC,0CAA0C,EAAEsE,UAAU,EAAE;QACnF;QAEA,MAAM,IAAI,CAACK,UAAU,CAACJ,WAAWE,YAAYC;IAC9C;IAEA,MAAcC,WACbJ,SAA2B,EAC3BK,aAAwC,EACxCC,OAAe,EACC;QAChB,IAAIN,UAAU5D,IAAI,KAAK,QAAQ;YAC9B,MAAMoB,OAAO,MAAM,AAACwC,UAAmCvC,OAAO;YAC9D,MAAM8C,iBAAiB,MAAMF,cAAcjD,aAAa,CAACkD,SAAS;gBAAE7B,QAAQ;YAAK;YACjF,MAAMU,WAAW,MAAMoB,eAAenB,cAAc;YACpD,MAAMD,SAASE,KAAK,CAAC7B;YACrB,MAAM2B,SAASG,KAAK;QACrB,OAAO,IAAIU,UAAU5D,IAAI,KAAK,aAAa;YAC1C,MAAMoE,eAAe,MAAMH,cAAcnD,kBAAkB,CAACoD,SAAS;gBAAE7B,QAAQ;YAAK;YACpF,WAAW,MAAML,SAAS,AAAC4B,UAAwC3B,MAAM,GAAI;gBAC5E,MAAM,IAAI,CAAC+B,UAAU,CAAChC,OAAOoC,cAAcpC,MAAMvC,IAAI;YACtD;QACD;IACD;IAEA,MAAM4E,UAAU3C,IAAe,EAAEI,OAAwB,EAAmB;QAC3E,sEAAsE;QACtE,MAAM1B,SAASsB,KAAKvB,IAAI,CAACC,MAAM;QAC/B,IAAIA,QAAQJ,SAAS,QAAQ;YAC5B,yDAAyD;YACzD,OAAO;QACR;QACA,MAAMoB,OAAO,MAAM,AAAChB,OAAgCiB,OAAO;QAC3D,OAAOiD,IAAIC,eAAe,CAACnD;IAC5B;IAEAoD,mBAA0B;QACzB,MAAM,IAAIlF,MAAM;IACjB;IAEAmF,oBAA2B;QAC1B,MAAM,IAAInF,MAAM;IACjB;IAEAoF,uBAA8B;QAC7B,MAAM,IAAIpF,MAAM;IACjB;IAEAqF,uBAA8B;QAC7B,MAAM,IAAIrF,MAAM;IACjB;AACD"}
|
|
1
|
+
{"version":3,"sources":["../../src/fs/createBrowserFileSystem.ts"],"sourcesContent":["import { dirname, join, normalize } from 'pathe';\nimport type {\n\tCopyOptions,\n\tIFileStat,\n\tIFileSystem,\n\tMkdirOptions,\n\tReaddirOptions,\n\tReadFileOptions,\n\tRenameOptions,\n\tRmOptions,\n\tWriteFileOptions,\n} from './IFileSystem';\nimport type { FileUrlOptions } from './types';\n\nclass BrowserFSError extends Error {\n\tconstructor(\n\t\tmessage: string,\n\t\tpublic code?: string,\n\t) {\n\t\tsuper(message);\n\t\tthis.name = 'BrowserFSError';\n\t}\n}\n\ntype BrowserFile = IFileStat & {\n\tkind: 'file';\n\tmeta: {\n\t\thandle: FileSystemFileHandle;\n\t\tfile: File;\n\t};\n};\n\ntype BrowserDirectory = IFileStat & {\n\tkind: 'directory';\n\tmeta: {\n\t\thandle: FileSystemDirectoryHandle;\n\t};\n};\n\ntype _BrowserNode = BrowserFile | BrowserDirectory;\n\n/**\n * Creates an IFileSystem instance backed by the browser's File System Access API.\n * @param root A FileSystemDirectoryHandle, usually obtained from `window.showDirectoryPicker()`.\n */\nexport function createBrowserFileSystem(root: FileSystemDirectoryHandle): IFileSystem {\n\treturn new BrowserFS(root);\n}\n\nclass BrowserFS implements IFileSystem {\n\troot: FileSystemDirectoryHandle;\n\tnode: BrowserDirectory;\n\n\tconstructor(root: FileSystemDirectoryHandle) {\n\t\tthis.root = root;\n\t\tthis.node = {\n\t\t\tpath: '/',\n\t\t\tname: '',\n\t\t\tdirectory: '',\n\t\t\tkind: 'directory',\n\t\t\tmtime: 0,\n\t\t\tsize: 0,\n\t\t\tmeta: {\n\t\t\t\thandle: root,\n\t\t\t},\n\t\t};\n\t}\n\n\t/**\n\t * Resolves a path string to a file or directory handle. This is the core navigation logic.\n\t * @returns A tuple of [handle, parentHandle, name] or [null, parentHandle, name] if not found.\n\t */\n\tprivate async _getHandle(path: string): Promise<[FileSystemHandle | null, FileSystemDirectoryHandle | null, string]> {\n\t\tconst normalized = normalize(path);\n\t\tif (normalized === '/' || normalized === '.') {\n\t\t\treturn [this.root, null, this.root.name];\n\t\t}\n\n\t\tconst parts = normalized.split('/').filter((p) => p);\n\t\tconst name = parts.pop()!;\n\n\t\tlet parent: FileSystemDirectoryHandle = this.root;\n\t\tfor (const part of parts) {\n\t\t\ttry {\n\t\t\t\tparent = await parent.getDirectoryHandle(part);\n\t\t\t} catch (e: any) {\n\t\t\t\tif (e.name === 'NotFoundError' || e.name === 'TypeMismatchError') {\n\t\t\t\t\treturn [null, null, name]; // Parent path does not exist or is a file\n\t\t\t\t}\n\t\t\t\tthrow e;\n\t\t\t}\n\t\t}\n\n\t\ttry {\n\t\t\t// Check for file first, then directory\n\t\t\tconst handle = await parent.getFileHandle(name).catch(() => parent.getDirectoryHandle(name));\n\t\t\treturn [handle, parent, name];\n\t\t} catch (e: any) {\n\t\t\tif (e.name === 'NotFoundError') {\n\t\t\t\treturn [null, parent, name]; // Entry not found\n\t\t\t}\n\t\t\tthrow e;\n\t\t}\n\t}\n\n\t/**\n\t * Converts a FileSystemHandle into our standard IFileStat object.\n\t */\n\tprivate async _toFileStat(handle: FileSystemHandle, path: string): Promise<IFileStat> {\n\t\tconst isFile = handle.kind === 'file';\n\t\tconst file = isFile ? await (handle as FileSystemFileHandle).getFile() : undefined;\n\n\t\treturn {\n\t\t\tname: handle.name,\n\t\t\tkind: handle.kind,\n\t\t\tpath: path,\n\t\t\tdirectory: dirname(path),\n\t\t\tsize: file?.size ?? 0,\n\t\t\tmtime: file?.lastModified ?? Date.now(),\n\t\t\tmeta: { handle, file },\n\t\t};\n\t}\n\n\t// --- IFileSystem Implementation ---\n\n\tasync stat(path: string): Promise<IFileStat> {\n\t\tconst [handle] = await this._getHandle(path);\n\t\tif (!handle) {\n\t\t\tthrow new BrowserFSError(`Path not found: ${path}`, 'ENOENT');\n\t\t}\n\t\treturn this._toFileStat(handle, path);\n\t}\n\n\tasync exists(path: string): Promise<boolean> {\n\t\tconst [handle] = await this._getHandle(path);\n\t\treturn !!handle;\n\t}\n\n\tasync readdir(dir: string, _options?: ReaddirOptions): Promise<IFileStat[]> {\n\t\tconst [handle] = await this._getHandle(dir);\n\t\tif (!handle) {\n\t\t\tthrow new BrowserFSError(`Directory not found: ${dir}`, 'ENOENT');\n\t\t}\n\t\tif (handle.kind !== 'directory') {\n\t\t\tthrow new BrowserFSError(`Not a directory: ${dir}`, 'ENOTDIR');\n\t\t}\n\n\t\tconst entries: IFileStat[] = [];\n\t\t// FileSystemDirectoryHandle.values() is part of the File System Access API\n\t\t// but not yet in TypeScript's lib.dom.d.ts\n\t\tfor await (const entry of (handle as any).values()) {\n\t\t\tentries.push(await this._toFileStat(entry, join(dir, entry.name)));\n\t\t}\n\t\treturn entries;\n\t}\n\n\tasync mkdir(path: string, options: MkdirOptions = {}): Promise<void> {\n\t\tconst parts = normalize(path)\n\t\t\t.split('/')\n\t\t\t.filter((p) => p);\n\t\tlet currentDir = this.root;\n\n\t\tfor (const part of parts) {\n\t\t\ttry {\n\t\t\t\tcurrentDir = await currentDir.getDirectoryHandle(part, { create: options.recursive });\n\t\t\t} catch (e: any) {\n\t\t\t\t// If recursive is false, getDirectoryHandle throws if a segment is missing.\n\t\t\t\t// We re-throw a more standard error.\n\t\t\t\tif (e.name === 'NotFoundError' && !options.recursive) {\n\t\t\t\t\tthrow new BrowserFSError(`Cannot create directory: Parent does not exist for path ${path}`, 'ENOENT');\n\t\t\t\t}\n\t\t\t\t// If it's another error (like a file with the same name), re-throw it.\n\t\t\t\tthrow e;\n\t\t\t}\n\t\t}\n\t}\n\n\treadFile(path: string, options?: ReadFileOptions & { encoding: 'text' }): Promise<string>;\n\treadFile(path: string, options?: ReadFileOptions): Promise<Uint8Array>;\n\tasync readFile(path: string, options?: ReadFileOptions): Promise<string | Uint8Array> {\n\t\tconst [handle] = await this._getHandle(path);\n\t\tif (!handle || handle.kind !== 'file') {\n\t\t\tthrow new BrowserFSError(`File not found: ${path}`, 'ENOENT');\n\t\t}\n\t\tconst file = await (handle as FileSystemFileHandle).getFile();\n\t\treturn options?.encoding === 'text' ? file.text() : file.bytes();\n\t}\n\n\tasync writeFile(path: string, data: any, options: WriteFileOptions = {}): Promise<void> {\n\t\tconst { overwrite = true } = options;\n\t\tconst [handle, parent, name] = await this._getHandle(path);\n\n\t\tif (!parent) {\n\t\t\tthrow new BrowserFSError(`Parent directory does not exist for path: ${path}`, 'ENOENT');\n\t\t}\n\t\tif (handle && !overwrite) {\n\t\t\tthrow new BrowserFSError(`File already exists: ${path}`, 'EEXIST');\n\t\t}\n\n\t\tconst fileHandle = await parent.getFileHandle(name, { create: true });\n\t\tconst writable = await fileHandle.createWritable();\n\t\tawait writable.write(data);\n\t\tawait writable.close();\n\t}\n\n\tasync rm(path: string, options: RmOptions = {}): Promise<void> {\n\t\tconst { recursive = false, force = false } = options;\n\t\tconst [handle, parent, name] = await this._getHandle(path);\n\n\t\tif (!handle) {\n\t\t\tif (force) return; // If force is true, do not error on not found.\n\t\t\tthrow new BrowserFSError(`Path not found: ${path}`, 'ENOENT');\n\t\t}\n\t\tif (!parent) {\n\t\t\tthrow new BrowserFSError(`Cannot remove root directory`);\n\t\t}\n\n\t\tawait parent.removeEntry(name, { recursive });\n\t}\n\n\tasync rename(oldPath: string, newPath: string, options?: RenameOptions): Promise<void> {\n\t\t// The File System Access API does NOT have a native move/rename.\n\t\t// The standard workaround is to copy, then delete the original.\n\t\tawait this.copy(oldPath, newPath, options);\n\t\tawait this.rm(oldPath, { recursive: true });\n\t}\n\n\tasync copy(srcPath: string, destPath: string, options?: CopyOptions): Promise<void> {\n\t\tconst [srcHandle] = await this._getHandle(srcPath);\n\t\tif (!srcHandle) {\n\t\t\tthrow new BrowserFSError(`Source not found: ${srcPath}`, 'ENOENT');\n\t\t}\n\n\t\tconst [destHandle, destParent, destName] = await this._getHandle(destPath);\n\t\tif (!destParent) {\n\t\t\tthrow new BrowserFSError(`Destination directory does not exist: ${dirname(destPath)}`, 'ENOENT');\n\t\t}\n\t\tif (destHandle && !options?.overwrite) {\n\t\t\tthrow new BrowserFSError(`Destination already exists: ${destPath}`, 'EEXIST');\n\t\t}\n\t\tif (destHandle?.kind === 'directory' && srcHandle.kind === 'file') {\n\t\t\tthrow new BrowserFSError(`Cannot overwrite a directory with a file: ${destPath}`, 'EISDIR');\n\t\t}\n\n\t\tawait this._copyEntry(srcHandle, destParent, destName);\n\t}\n\n\tprivate async _copyEntry(\n\t\tsrcHandle: FileSystemHandle,\n\t\tdestDirHandle: FileSystemDirectoryHandle,\n\t\tnewName: string,\n\t): Promise<void> {\n\t\tif (srcHandle.kind === 'file') {\n\t\t\tconst file = await (srcHandle as FileSystemFileHandle).getFile();\n\t\t\tconst destFileHandle = await destDirHandle.getFileHandle(newName, { create: true });\n\t\t\tconst writable = await destFileHandle.createWritable();\n\t\t\tawait writable.write(file);\n\t\t\tawait writable.close();\n\t\t} else if (srcHandle.kind === 'directory') {\n\t\t\tconst newDirHandle = await destDirHandle.getDirectoryHandle(newName, { create: true });\n\t\t\tfor await (const entry of (srcHandle as any).values()) {\n\t\t\t\tawait this._copyEntry(entry, newDirHandle, entry.name);\n\t\t\t}\n\t\t}\n\t}\n\n\tasync createUrl(stat: IFileStat, _options?: FileUrlOptions): Promise<string> {\n\t\t// The handle is stored in the meta property in our _toFileStat method\n\t\tconst handle = stat.meta.handle as FileSystemHandle | undefined;\n\t\tif (handle?.kind !== 'file') {\n\t\t\t// Return a placeholder or throw an error for directories\n\t\t\treturn '';\n\t\t}\n\t\tconst file = await (handle as FileSystemFileHandle).getFile();\n\t\treturn URL.createObjectURL(file);\n\t}\n\n\tcreateReadStream(): never {\n\t\tthrow new Error('Streaming is not implemented for BrowserFS. Use readFile instead.');\n\t}\n\n\tcreateWriteStream(): never {\n\t\tthrow new Error('Streaming is not implemented for BrowserFS. Use writeFile instead.');\n\t}\n\n\tcreateWritableStream(): never {\n\t\tthrow new Error('Streaming is not implemented for BrowserFS. Use writeFile instead.');\n\t}\n\n\tcreateReadableStream(): never {\n\t\tthrow new Error('Streaming is not implemented for BrowserFS. Use readFile instead.');\n\t}\n}\n"],"names":["dirname","join","normalize","BrowserFSError","Error","message","code","name","createBrowserFileSystem","root","BrowserFS","node","path","directory","kind","mtime","size","meta","handle","_getHandle","normalized","parts","split","filter","p","pop","parent","part","getDirectoryHandle","e","getFileHandle","catch","_toFileStat","isFile","file","getFile","undefined","lastModified","Date","now","stat","exists","readdir","dir","_options","entries","entry","values","push","mkdir","options","currentDir","create","recursive","readFile","encoding","text","bytes","writeFile","data","overwrite","fileHandle","writable","createWritable","write","close","rm","force","removeEntry","rename","oldPath","newPath","copy","srcPath","destPath","srcHandle","destHandle","destParent","destName","_copyEntry","destDirHandle","newName","destFileHandle","newDirHandle","createUrl","URL","createObjectURL","createReadStream","createWriteStream","createWritableStream","createReadableStream"],"mappings":"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"}
|
|
@@ -1,15 +1,14 @@
|
|
|
1
1
|
import { computeIfAbsent } from "@wener/utils";
|
|
2
2
|
import { basename, dirname, normalize } from "pathe";
|
|
3
|
+
import { FileSystemError } from "./FileSystemError.js";
|
|
3
4
|
import { findMimeType } from "./findMimeType.js";
|
|
4
5
|
export function createMemoryFileSystem(options = {}) {
|
|
5
6
|
return new MemFS(options);
|
|
6
7
|
}
|
|
7
|
-
let MemoryFileSystemError = class MemoryFileSystemError extends
|
|
8
|
-
code;
|
|
8
|
+
let MemoryFileSystemError = class MemoryFileSystemError extends FileSystemError {
|
|
9
9
|
constructor(message, code) {
|
|
10
|
-
super(message);
|
|
10
|
+
super(message, code);
|
|
11
11
|
this.name = "MemoryFileSystemError";
|
|
12
|
-
this.code = code;
|
|
13
12
|
}
|
|
14
13
|
};
|
|
15
14
|
let MemFS = class MemFS {
|
|
@@ -338,7 +337,7 @@ let MemFS = class MemFS {
|
|
|
338
337
|
const chunks = [];
|
|
339
338
|
const self = this;
|
|
340
339
|
const stream = new Writable({
|
|
341
|
-
write(chunk,
|
|
340
|
+
write(chunk, _encoding, callback) {
|
|
342
341
|
if (signal?.aborted) {
|
|
343
342
|
callback(new MemoryFileSystemError("Operation aborted", "ABORT_ERR"));
|
|
344
343
|
return;
|
|
@@ -412,8 +411,10 @@ let MemFS = class MemFS {
|
|
|
412
411
|
let c = computeIfAbsent(this.cache, mf, () => ({}));
|
|
413
412
|
if (!c.url) {
|
|
414
413
|
let mime = findMimeType(mf.name) || "application/octet-stream";
|
|
414
|
+
// Convert Buffer to Uint8Array for Blob compatibility with TypeScript 5.x strict typing
|
|
415
|
+
const blobPart = typeof mf.content === "string" ? mf.content : new Uint8Array(mf.content);
|
|
415
416
|
c.url = URL.createObjectURL(new Blob([
|
|
416
|
-
|
|
417
|
+
blobPart
|
|
417
418
|
], {
|
|
418
419
|
type: mime
|
|
419
420
|
}));
|