jww-parser 2025.12.3 → 2025.12.4

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/dist/index.js CHANGED
@@ -1,768 +1,846 @@
1
- "use strict";
2
- var __create = Object.create;
3
- var __defProp = Object.defineProperty;
4
- var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
5
- var __getOwnPropNames = Object.getOwnPropertyNames;
6
- var __getProtoOf = Object.getPrototypeOf;
7
- var __hasOwnProp = Object.prototype.hasOwnProperty;
8
- var __export = (target, all) => {
9
- for (var name in all)
10
- __defProp(target, name, { get: all[name], enumerable: true });
11
- };
12
- var __copyProps = (to, from, except, desc) => {
13
- if (from && typeof from === "object" || typeof from === "function") {
14
- for (let key of __getOwnPropNames(from))
15
- if (!__hasOwnProp.call(to, key) && key !== except)
16
- __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
17
- }
18
- return to;
19
- };
20
- var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
21
- // If the importer is in node compatibility mode or this is not an ESM
22
- // file that has been converted to a CommonJS file using a Babel-
23
- // compatible transform (i.e. "__esModule" has not been set), then set
24
- // "default" to the CommonJS "module.exports" for node compatibility.
25
- isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
26
- mod
27
- ));
28
- var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
29
-
30
- // src/index.ts
31
- var index_exports = {};
32
- __export(index_exports, {
33
- JwwErrorCode: () => JwwErrorCode,
34
- JwwParser: () => JwwParser,
35
- JwwParserError: () => JwwParserError,
36
- NotInitializedError: () => NotInitializedError,
37
- ParseError: () => ParseError,
38
- ValidationError: () => ValidationError,
39
- WasmLoadError: () => WasmLoadError,
40
- createParser: () => createParser,
41
- default: () => index_default,
42
- isJwwFile: () => isJwwFile,
43
- quickValidate: () => quickValidate
44
- });
45
- module.exports = __toCommonJS(index_exports);
46
- var import_meta = {};
47
- var JwwErrorCode = /* @__PURE__ */ ((JwwErrorCode2) => {
48
- JwwErrorCode2["NOT_INITIALIZED"] = "NOT_INITIALIZED";
49
- JwwErrorCode2["WASM_LOAD_FAILED"] = "WASM_LOAD_FAILED";
50
- JwwErrorCode2["WASM_TIMEOUT"] = "WASM_TIMEOUT";
51
- JwwErrorCode2["INVALID_SIGNATURE"] = "INVALID_SIGNATURE";
52
- JwwErrorCode2["UNSUPPORTED_VERSION"] = "UNSUPPORTED_VERSION";
53
- JwwErrorCode2["PARSE_ERROR"] = "PARSE_ERROR";
54
- JwwErrorCode2["CONVERSION_ERROR"] = "CONVERSION_ERROR";
55
- JwwErrorCode2["VALIDATION_ERROR"] = "VALIDATION_ERROR";
56
- JwwErrorCode2["MEMORY_ERROR"] = "MEMORY_ERROR";
57
- JwwErrorCode2["INVALID_ARGUMENT"] = "INVALID_ARGUMENT";
58
- return JwwErrorCode2;
59
- })(JwwErrorCode || {});
60
- var JwwParserError = class _JwwParserError extends Error {
61
- constructor(code, message, options) {
62
- super(message);
63
- this.name = "JwwParserError";
64
- this.code = code;
65
- this.cause = options?.cause;
66
- this.context = options?.context;
67
- this.timestamp = /* @__PURE__ */ new Date();
68
- if (Error.captureStackTrace) {
69
- Error.captureStackTrace(this, _JwwParserError);
70
- }
71
- }
72
- /**
73
- * Returns a detailed string representation of the error
74
- */
75
- toDetailedString() {
76
- let details = `[${this.code}] ${this.message}`;
77
- if (this.context) {
78
- details += `
79
- Context: ${JSON.stringify(this.context, null, 2)}`;
80
- }
81
- if (this.cause) {
82
- details += `
83
- Caused by: ${this.cause.message}`;
84
- }
85
- return details;
86
- }
87
- /**
88
- * Converts the error to a plain object for logging/serialization
89
- */
90
- toJSON() {
91
- return {
92
- name: this.name,
93
- code: this.code,
94
- message: this.message,
95
- context: this.context,
96
- timestamp: this.timestamp.toISOString(),
97
- cause: this.cause?.message,
98
- stack: this.stack
99
- };
100
- }
101
- };
102
- var NotInitializedError = class extends JwwParserError {
103
- constructor() {
104
- super(
105
- "NOT_INITIALIZED" /* NOT_INITIALIZED */,
106
- "Parser not initialized. Call init() first or use createParser() factory function."
107
- );
108
- this.name = "NotInitializedError";
109
- }
110
- };
111
- var WasmLoadError = class extends JwwParserError {
112
- constructor(message, cause) {
113
- super("WASM_LOAD_FAILED" /* WASM_LOAD_FAILED */, message, { cause });
114
- this.name = "WasmLoadError";
115
- }
116
- };
117
- var ValidationError = class extends JwwParserError {
118
- constructor(message, issues) {
119
- super("VALIDATION_ERROR" /* VALIDATION_ERROR */, message, {
120
- context: { issues }
121
- });
122
- this.name = "ValidationError";
123
- this.issues = issues;
124
- }
125
- };
126
- var ParseError = class extends JwwParserError {
127
- constructor(message, options) {
128
- super("PARSE_ERROR" /* PARSE_ERROR */, message, {
129
- cause: options?.cause,
130
- context: {
131
- offset: options?.offset,
132
- section: options?.section
133
- }
134
- });
135
- this.name = "ParseError";
136
- this.offset = options?.offset;
137
- this.section = options?.section;
138
- }
139
- };
1
+ /**
2
+ * JWW (Jw_cad) file parser and DXF converter
3
+ *
4
+ * This module provides functionality to parse JWW binary files
5
+ * and convert them to DXF format using WebAssembly.
6
+ *
7
+ * @packageDocumentation
8
+ */
9
+ // =============================================================================
10
+ // Error Classes
11
+ // =============================================================================
12
+ /**
13
+ * Error codes for JWW parser operations
14
+ */
15
+ export var JwwErrorCode;
16
+ (function (JwwErrorCode) {
17
+ /** Parser has not been initialized */
18
+ JwwErrorCode["NOT_INITIALIZED"] = "NOT_INITIALIZED";
19
+ /** WASM module failed to load */
20
+ JwwErrorCode["WASM_LOAD_FAILED"] = "WASM_LOAD_FAILED";
21
+ /** WASM functions not available after timeout */
22
+ JwwErrorCode["WASM_TIMEOUT"] = "WASM_TIMEOUT";
23
+ /** Invalid JWW file signature */
24
+ JwwErrorCode["INVALID_SIGNATURE"] = "INVALID_SIGNATURE";
25
+ /** Unsupported JWW version */
26
+ JwwErrorCode["UNSUPPORTED_VERSION"] = "UNSUPPORTED_VERSION";
27
+ /** General parse error */
28
+ JwwErrorCode["PARSE_ERROR"] = "PARSE_ERROR";
29
+ /** DXF conversion error */
30
+ JwwErrorCode["CONVERSION_ERROR"] = "CONVERSION_ERROR";
31
+ /** Validation error */
32
+ JwwErrorCode["VALIDATION_ERROR"] = "VALIDATION_ERROR";
33
+ /** Memory allocation error */
34
+ JwwErrorCode["MEMORY_ERROR"] = "MEMORY_ERROR";
35
+ /** Invalid argument provided */
36
+ JwwErrorCode["INVALID_ARGUMENT"] = "INVALID_ARGUMENT";
37
+ })(JwwErrorCode || (JwwErrorCode = {}));
38
+ /**
39
+ * Base error class for JWW parser errors
40
+ */
41
+ export class JwwParserError extends Error {
42
+ constructor(code, message, options) {
43
+ super(message);
44
+ this.name = "JwwParserError";
45
+ this.code = code;
46
+ this.cause = options?.cause;
47
+ this.context = options?.context;
48
+ this.timestamp = new Date();
49
+ // Maintain proper stack trace for where our error was thrown
50
+ if (Error.captureStackTrace) {
51
+ Error.captureStackTrace(this, JwwParserError);
52
+ }
53
+ }
54
+ /**
55
+ * Returns a detailed string representation of the error
56
+ */
57
+ toDetailedString() {
58
+ let details = `[${this.code}] ${this.message}`;
59
+ if (this.context) {
60
+ details += `\nContext: ${JSON.stringify(this.context, null, 2)}`;
61
+ }
62
+ if (this.cause) {
63
+ details += `\nCaused by: ${this.cause.message}`;
64
+ }
65
+ return details;
66
+ }
67
+ /**
68
+ * Converts the error to a plain object for logging/serialization
69
+ */
70
+ toJSON() {
71
+ return {
72
+ name: this.name,
73
+ code: this.code,
74
+ message: this.message,
75
+ context: this.context,
76
+ timestamp: this.timestamp.toISOString(),
77
+ cause: this.cause?.message,
78
+ stack: this.stack,
79
+ };
80
+ }
81
+ }
82
+ /**
83
+ * Error thrown when the parser is not initialized
84
+ */
85
+ export class NotInitializedError extends JwwParserError {
86
+ constructor() {
87
+ super(JwwErrorCode.NOT_INITIALIZED, "Parser not initialized. Call init() first or use createParser() factory function.");
88
+ this.name = "NotInitializedError";
89
+ }
90
+ }
91
+ /**
92
+ * Error thrown when WASM module fails to load
93
+ */
94
+ export class WasmLoadError extends JwwParserError {
95
+ constructor(message, cause) {
96
+ super(JwwErrorCode.WASM_LOAD_FAILED, message, { cause });
97
+ this.name = "WasmLoadError";
98
+ }
99
+ }
100
+ /**
101
+ * Error thrown when file validation fails
102
+ */
103
+ export class ValidationError extends JwwParserError {
104
+ constructor(message, issues) {
105
+ super(JwwErrorCode.VALIDATION_ERROR, message, {
106
+ context: { issues },
107
+ });
108
+ this.name = "ValidationError";
109
+ this.issues = issues;
110
+ }
111
+ }
112
+ /**
113
+ * Error thrown during parsing
114
+ */
115
+ export class ParseError extends JwwParserError {
116
+ constructor(message, options) {
117
+ super(JwwErrorCode.PARSE_ERROR, message, {
118
+ cause: options?.cause,
119
+ context: {
120
+ offset: options?.offset,
121
+ section: options?.section,
122
+ },
123
+ });
124
+ this.name = "ParseError";
125
+ this.offset = options?.offset;
126
+ this.section = options?.section;
127
+ }
128
+ }
129
+ // =============================================================================
130
+ // Helper Functions
131
+ // =============================================================================
132
+ /**
133
+ * Format bytes to human-readable string
134
+ */
140
135
  function formatBytes(bytes) {
141
- if (bytes < 1024) return `${bytes} B`;
142
- if (bytes < 1024 * 1024) return `${(bytes / 1024).toFixed(2)} KB`;
143
- if (bytes < 1024 * 1024 * 1024)
144
- return `${(bytes / (1024 * 1024)).toFixed(2)} MB`;
145
- return `${(bytes / (1024 * 1024 * 1024)).toFixed(2)} GB`;
136
+ if (bytes < 1024)
137
+ return `${bytes} B`;
138
+ if (bytes < 1024 * 1024)
139
+ return `${(bytes / 1024).toFixed(2)} KB`;
140
+ if (bytes < 1024 * 1024 * 1024)
141
+ return `${(bytes / (1024 * 1024)).toFixed(2)} MB`;
142
+ return `${(bytes / (1024 * 1024 * 1024)).toFixed(2)} GB`;
146
143
  }
144
+ /**
145
+ * Get file size category based on byte count
146
+ */
147
147
  function getSizeCategory(bytes) {
148
- if (bytes < 100 * 1024) return "small";
149
- if (bytes < 1024 * 1024) return "medium";
150
- if (bytes < 10 * 1024 * 1024) return "large";
151
- return "very_large";
148
+ if (bytes < 100 * 1024)
149
+ return "small"; // < 100KB
150
+ if (bytes < 1024 * 1024)
151
+ return "medium"; // < 1MB
152
+ if (bytes < 10 * 1024 * 1024)
153
+ return "large"; // < 10MB
154
+ return "very_large"; // >= 10MB
152
155
  }
156
+ /**
157
+ * Create a parse error from WASM result
158
+ */
153
159
  function createParseError(result) {
154
- const message = result.error || "Parse failed";
155
- if (message.includes("invalid JWW signature")) {
156
- return new ParseError("Invalid JWW file format: missing or corrupt signature", {
157
- section: "header",
158
- offset: 0
159
- });
160
- }
161
- if (message.includes("unsupported") && message.includes("version")) {
162
- return new ParseError(message, {
163
- section: "header"
164
- });
165
- }
166
- return new ParseError(message, {
167
- offset: result.offset,
168
- section: result.section
169
- });
170
- }
171
- var JwwParser = class {
172
- /**
173
- * Create a new JWW parser instance
174
- * @param wasmPath - Path to the jww-parser.wasm file
175
- */
176
- constructor(wasmPath) {
177
- this.initialized = false;
178
- this.initPromise = null;
179
- this.goInstance = null;
180
- this.wasmInstance = null;
181
- // Debug and logging
182
- this.debugOptions = { enabled: false };
183
- this.debugLogs = [];
184
- // Statistics
185
- this.stats = {
186
- parseCount: 0,
187
- totalBytesProcessed: 0,
188
- parseTimes: [],
189
- errorCount: 0
190
- };
191
- this.wasmPath = wasmPath || this.getDefaultWasmPath();
192
- }
193
- getDefaultWasmPath() {
194
- if (typeof process !== "undefined" && process.versions?.node) {
195
- return new URL("../wasm/jww-parser.wasm", import_meta.url).pathname;
196
- }
197
- return "jww-parser.wasm";
198
- }
199
- // ===========================================================================
200
- // Debug Methods
201
- // ===========================================================================
202
- /**
203
- * Enable or configure debug mode
204
- * @param options - Debug configuration options
205
- */
206
- setDebug(options) {
207
- if (typeof options === "boolean") {
208
- this.debugOptions = { enabled: options };
209
- } else {
210
- this.debugOptions = { ...this.debugOptions, ...options };
211
- }
212
- if (this.initialized && typeof globalThis.jwwSetDebug === "function") {
213
- globalThis.jwwSetDebug(this.debugOptions.enabled ?? false);
214
- }
215
- this.log("info", "Debug mode " + (this.debugOptions.enabled ? "enabled" : "disabled"));
216
- }
217
- /**
218
- * Get debug logs
219
- * @param level - Optional minimum log level to filter
220
- * @returns Array of debug log entries
221
- */
222
- getDebugLogs(level) {
223
- if (!level) return [...this.debugLogs];
224
- const levels = ["debug", "info", "warn", "error"];
225
- const minIndex = levels.indexOf(level);
226
- return this.debugLogs.filter(
227
- (entry) => levels.indexOf(entry.level) >= minIndex
228
- );
229
- }
230
- /**
231
- * Clear debug logs
232
- */
233
- clearDebugLogs() {
234
- this.debugLogs = [];
235
- }
236
- log(level, message, data) {
237
- if (!this.debugOptions.enabled) return;
238
- const levels = ["debug", "info", "warn", "error"];
239
- const minLevel = this.debugOptions.logLevel || "info";
240
- if (levels.indexOf(level) < levels.indexOf(minLevel)) return;
241
- const entry = {
242
- timestamp: /* @__PURE__ */ new Date(),
243
- level,
244
- message,
245
- data: this.debugOptions.includeMemoryUsage ? { ...data, memory: this.getMemoryStats() } : data
246
- };
247
- this.debugLogs.push(entry);
248
- if (this.debugLogs.length > (this.debugOptions.maxLogEntries || 1e3)) {
249
- this.debugLogs.shift();
250
- }
251
- if (this.debugOptions.onDebug) {
252
- this.debugOptions.onDebug(entry);
253
- }
254
- if (this.debugOptions.logToConsole) {
255
- const consoleFn = level === "error" ? console.error : level === "warn" ? console.warn : level === "debug" ? console.debug : console.log;
256
- consoleFn(`[JwwParser:${level}] ${message}`, data || "");
257
- }
258
- }
259
- // ===========================================================================
260
- // Initialization Methods
261
- // ===========================================================================
262
- /**
263
- * Check if the parser is initialized
264
- */
265
- isInitialized() {
266
- return this.initialized;
267
- }
268
- /**
269
- * Initialize the WASM module
270
- * Must be called before using parse methods
271
- */
272
- async init() {
273
- if (this.initialized) return;
274
- if (this.initPromise) return this.initPromise;
275
- this.log("info", "Initializing WASM module");
276
- const startTime = Date.now();
277
- this.initPromise = this.loadWasm();
278
- try {
279
- await this.initPromise;
280
- this.initialized = true;
281
- this.log("info", "WASM module initialized", {
282
- elapsedMs: Date.now() - startTime
283
- });
284
- } catch (error) {
285
- this.initPromise = null;
286
- this.log("error", "WASM initialization failed", {
287
- error: error instanceof Error ? error.message : String(error)
288
- });
289
- throw error;
290
- }
291
- }
292
- async loadWasm() {
293
- if (typeof Go === "undefined") {
294
- await this.loadWasmExec();
295
- }
296
- this.goInstance = new Go();
297
- try {
298
- if (typeof process !== "undefined" && process.versions?.node) {
299
- const fs = await import("fs");
300
- const wasmBuffer = fs.readFileSync(this.wasmPath);
301
- const wasmModule = await WebAssembly.compile(wasmBuffer);
302
- this.wasmInstance = await WebAssembly.instantiate(
303
- wasmModule,
304
- this.goInstance.importObject
305
- );
306
- } else {
307
- const result = await WebAssembly.instantiateStreaming(
308
- fetch(this.wasmPath),
309
- this.goInstance.importObject
310
- ).catch(async () => {
311
- const response = await fetch(this.wasmPath);
312
- if (!response.ok) {
313
- throw new WasmLoadError(
314
- `Failed to fetch WASM file: ${response.status} ${response.statusText}`
315
- );
316
- }
317
- const bytes = await response.arrayBuffer();
318
- return WebAssembly.instantiate(bytes, this.goInstance.importObject);
160
+ const message = result.error || "Parse failed";
161
+ // Try to extract more specific error type
162
+ if (message.includes("invalid JWW signature")) {
163
+ return new ParseError("Invalid JWW file format: missing or corrupt signature", {
164
+ section: "header",
165
+ offset: 0,
319
166
  });
320
- this.wasmInstance = result.instance;
321
- }
322
- } catch (error) {
323
- throw new WasmLoadError(
324
- `Failed to load WASM module: ${error instanceof Error ? error.message : String(error)}`,
325
- error instanceof Error ? error : void 0
326
- );
327
- }
328
- this.goInstance.run(this.wasmInstance);
329
- await this.waitForWasmFunctions();
330
- if (this.debugOptions.enabled && typeof globalThis.jwwSetDebug === "function") {
331
- globalThis.jwwSetDebug(true);
332
- }
333
- }
334
- async loadWasmExec() {
335
- if (typeof process !== "undefined" && process.versions?.node) {
336
- const wasmExecPath = new URL("../wasm/wasm_exec.js", import_meta.url).pathname;
337
- await import(wasmExecPath);
338
- } else {
339
- throw new WasmLoadError(
340
- "Go runtime not loaded. Please include wasm_exec.js in your HTML before using the parser."
341
- );
342
- }
343
- }
344
- async waitForWasmFunctions(timeout = 5e3, interval = 50) {
345
- const start = Date.now();
346
- while (Date.now() - start < timeout) {
347
- if (typeof globalThis.jwwParse === "function" && typeof globalThis.jwwToDxf === "function" && typeof globalThis.jwwToDxfString === "function") {
348
- return;
349
- }
350
- await new Promise((resolve) => setTimeout(resolve, interval));
351
- }
352
- throw new JwwParserError(
353
- "WASM_TIMEOUT" /* WASM_TIMEOUT */,
354
- "WASM functions not available after timeout. The WASM module may have failed to initialize."
355
- );
356
- }
357
- ensureInitialized() {
358
- if (!this.initialized) {
359
- throw new NotInitializedError();
360
- }
361
- }
362
- // ===========================================================================
363
- // Validation Methods
364
- // ===========================================================================
365
- /**
366
- * Validate a JWW file without fully parsing it
367
- * Useful for checking file validity before processing
368
- *
369
- * @param data - JWW file content as Uint8Array
370
- * @returns Validation result with any issues found
371
- */
372
- validate(data) {
373
- const startTime = Date.now();
374
- const issues = [];
375
- if (data.length < 16) {
376
- issues.push({
377
- severity: "error",
378
- code: "FILE_TOO_SMALL",
379
- message: "File is too small to be a valid JWW file",
380
- details: { size: data.length, minimumRequired: 16 }
381
- });
382
167
  }
383
- const signature = new TextDecoder().decode(data.slice(0, 8));
384
- if (signature !== "JwwData.") {
385
- issues.push({
386
- severity: "error",
387
- code: "INVALID_SIGNATURE",
388
- message: `Invalid file signature: expected "JwwData.", got "${signature.replace(/[^\x20-\x7E]/g, "?")}"`,
389
- offset: 0
390
- });
391
- }
392
- let version;
393
- if (data.length >= 12) {
394
- const view = new DataView(data.buffer, data.byteOffset, data.byteLength);
395
- version = view.getInt32(8, true);
396
- if (version < 200 || version > 1e3) {
397
- issues.push({
398
- severity: "warning",
399
- code: "UNUSUAL_VERSION",
400
- message: `Unusual version number: ${version}. Expected between 200-1000.`,
401
- details: { version }
168
+ if (message.includes("unsupported") && message.includes("version")) {
169
+ return new ParseError(message, {
170
+ section: "header",
402
171
  });
403
- }
404
- }
405
- const estimatedEntityCount = Math.floor(data.length / 75);
406
- const sizeCategory = getSizeCategory(data.length);
407
- if (sizeCategory === "very_large") {
408
- issues.push({
409
- severity: "warning",
410
- code: "LARGE_FILE",
411
- message: `File is very large (${formatBytes(data.length)}). Consider using streaming mode for better performance.`,
412
- details: { size: data.length, sizeFormatted: formatBytes(data.length) }
413
- });
414
- }
415
- const hasErrors = issues.some((i) => i.severity === "error");
416
- return {
417
- valid: !hasErrors,
418
- version,
419
- sizeCategory,
420
- estimatedEntityCount,
421
- issues,
422
- validationTimeMs: Date.now() - startTime
423
- };
424
- }
425
- /**
426
- * Validate and throw if invalid
427
- * Convenience method that throws a ValidationError if the file is invalid
428
- *
429
- * @param data - JWW file content as Uint8Array
430
- * @throws {ValidationError} If the file is invalid
431
- */
432
- validateOrThrow(data) {
433
- const result = this.validate(data);
434
- if (!result.valid) {
435
- throw new ValidationError(
436
- "File validation failed: " + result.issues.filter((i) => i.severity === "error").map((i) => i.message).join("; "),
437
- result.issues
438
- );
439
- }
440
- }
441
- // ===========================================================================
442
- // Parsing Methods
443
- // ===========================================================================
444
- /**
445
- * Parse a JWW file and return the document structure
446
- *
447
- * @param data - JWW file content as Uint8Array
448
- * @param options - Optional parsing options
449
- * @returns Parsed JWW document
450
- * @throws {NotInitializedError} If parser is not initialized
451
- * @throws {ParseError} If parsing fails
452
- */
453
- parse(data, options) {
454
- this.ensureInitialized();
455
- if (options?.signal?.aborted) {
456
- throw new JwwParserError(
457
- "PARSE_ERROR" /* PARSE_ERROR */,
458
- "Parse operation was aborted"
459
- );
460
- }
461
- const startTime = Date.now();
462
- this.log("info", "Starting parse operation", {
463
- dataSize: data.length,
464
- options: options ? { ...options, onProgress: void 0 } : void 0
172
+ }
173
+ return new ParseError(message, {
174
+ offset: result.offset,
175
+ section: result.section,
465
176
  });
466
- if (options?.onProgress) {
467
- options.onProgress({
468
- stage: "loading",
469
- progress: 0,
470
- overallProgress: 0,
471
- message: "Loading file data",
472
- elapsedMs: 0
473
- });
474
- }
475
- try {
476
- const result = globalThis.jwwParse(data);
477
- if (!result.ok) {
478
- this.stats.errorCount++;
479
- throw createParseError(result);
480
- }
481
- if (options?.onProgress) {
482
- options.onProgress({
483
- stage: "complete",
484
- progress: 100,
485
- overallProgress: 100,
486
- message: "Parsing complete",
487
- elapsedMs: Date.now() - startTime
488
- });
489
- }
490
- const doc = JSON.parse(result.data);
491
- let filteredDoc = doc;
492
- if (options) {
493
- filteredDoc = this.applyParseOptions(doc, options);
494
- }
495
- const parseTime = Date.now() - startTime;
496
- this.stats.parseCount++;
497
- this.stats.totalBytesProcessed += data.length;
498
- this.stats.parseTimes.push(parseTime);
499
- if (this.stats.parseTimes.length > 100) {
500
- this.stats.parseTimes.shift();
501
- }
502
- this.log("info", "Parse complete", {
503
- parseTimeMs: parseTime,
504
- entityCount: filteredDoc.Entities.length,
505
- blockCount: filteredDoc.Blocks.length
506
- });
507
- return filteredDoc;
508
- } catch (error) {
509
- this.stats.errorCount++;
510
- this.log("error", "Parse failed", {
511
- error: error instanceof Error ? error.message : String(error)
512
- });
513
- if (error instanceof JwwParserError) {
514
- throw error;
515
- }
516
- throw new ParseError(
517
- error instanceof Error ? error.message : String(error),
518
- { cause: error instanceof Error ? error : void 0 }
519
- );
520
- }
521
- }
522
- applyParseOptions(doc, options) {
523
- let entities = doc.Entities;
524
- let blocks = doc.Blocks;
525
- if (options.skipEntityTypes && options.skipEntityTypes.length > 0) {
526
- const skipTypes = new Set(options.skipEntityTypes);
527
- entities = entities.filter((e) => !skipTypes.has(e.Type));
528
- }
529
- if (options.layerGroupFilter && options.layerGroupFilter.length > 0) {
530
- const groups = new Set(options.layerGroupFilter);
531
- entities = entities.filter((e) => groups.has(e.LayerGroup));
532
- }
533
- if (options.layerFilter) {
534
- const filter = options.layerFilter;
535
- entities = entities.filter((e) => {
536
- const layers = filter[e.LayerGroup];
537
- return !layers || layers.includes(e.Layer);
538
- });
539
- }
540
- if (options.maxEntities && entities.length > options.maxEntities) {
541
- entities = entities.slice(0, options.maxEntities);
542
- }
543
- if (options.includeBlocks === false) {
544
- blocks = [];
545
- }
546
- return {
547
- ...doc,
548
- Entities: entities,
549
- Blocks: blocks
550
- };
551
- }
552
- /**
553
- * Parse a JWW file and convert to DXF document structure
554
- *
555
- * @param data - JWW file content as Uint8Array
556
- * @param options - Optional conversion options
557
- * @returns DXF document object
558
- */
559
- toDxf(data, options) {
560
- this.ensureInitialized();
561
- const startTime = Date.now();
562
- this.log("info", "Starting DXF conversion", { dataSize: data.length });
563
- if (options?.onProgress) {
564
- options.onProgress({
565
- stage: "converting",
566
- progress: 0,
567
- overallProgress: 0,
568
- message: "Converting to DXF",
569
- elapsedMs: 0
570
- });
571
- }
572
- try {
573
- const result = globalThis.jwwToDxf(data);
574
- if (!result.ok) {
575
- this.stats.errorCount++;
576
- throw createParseError(result);
577
- }
578
- if (options?.onProgress) {
579
- options.onProgress({
580
- stage: "complete",
581
- progress: 100,
582
- overallProgress: 100,
583
- message: "Conversion complete",
584
- elapsedMs: Date.now() - startTime
177
+ }
178
+ // =============================================================================
179
+ // JwwParser Class
180
+ // =============================================================================
181
+ /**
182
+ * JWW file parser with WebAssembly backend
183
+ *
184
+ * @example
185
+ * ```typescript
186
+ * // Using the factory function (recommended)
187
+ * const parser = await createParser();
188
+ * const doc = parser.parse(fileData);
189
+ *
190
+ * // Manual initialization
191
+ * const parser = new JwwParser();
192
+ * await parser.init();
193
+ * const doc = parser.parse(fileData);
194
+ * ```
195
+ */
196
+ export class JwwParser {
197
+ /**
198
+ * Create a new JWW parser instance
199
+ * @param wasmPath - Path to the jww-parser.wasm file
200
+ */
201
+ constructor(wasmPath) {
202
+ this.initialized = false;
203
+ this.initPromise = null;
204
+ this.goInstance = null;
205
+ this.wasmInstance = null;
206
+ // Debug and logging
207
+ this.debugOptions = { enabled: false };
208
+ this.debugLogs = [];
209
+ // Statistics
210
+ this.stats = {
211
+ parseCount: 0,
212
+ totalBytesProcessed: 0,
213
+ parseTimes: [],
214
+ errorCount: 0,
215
+ };
216
+ this.wasmPath = wasmPath || this.getDefaultWasmPath();
217
+ }
218
+ getDefaultWasmPath() {
219
+ if (typeof process !== "undefined" && process.versions?.node) {
220
+ return new URL("../wasm/jww-parser.wasm", import.meta.url).pathname;
221
+ }
222
+ return "jww-parser.wasm";
223
+ }
224
+ // ===========================================================================
225
+ // Debug Methods
226
+ // ===========================================================================
227
+ /**
228
+ * Enable or configure debug mode
229
+ * @param options - Debug configuration options
230
+ */
231
+ setDebug(options) {
232
+ if (typeof options === "boolean") {
233
+ this.debugOptions = { enabled: options };
234
+ }
235
+ else {
236
+ this.debugOptions = { ...this.debugOptions, ...options };
237
+ }
238
+ // Also set debug mode in WASM if available
239
+ if (this.initialized && typeof globalThis.jwwSetDebug === "function") {
240
+ globalThis.jwwSetDebug(this.debugOptions.enabled ?? false);
241
+ }
242
+ this.log("info", "Debug mode " + (this.debugOptions.enabled ? "enabled" : "disabled"));
243
+ }
244
+ /**
245
+ * Get debug logs
246
+ * @param level - Optional minimum log level to filter
247
+ * @returns Array of debug log entries
248
+ */
249
+ getDebugLogs(level) {
250
+ if (!level)
251
+ return [...this.debugLogs];
252
+ const levels = ["debug", "info", "warn", "error"];
253
+ const minIndex = levels.indexOf(level);
254
+ return this.debugLogs.filter((entry) => levels.indexOf(entry.level) >= minIndex);
255
+ }
256
+ /**
257
+ * Clear debug logs
258
+ */
259
+ clearDebugLogs() {
260
+ this.debugLogs = [];
261
+ }
262
+ log(level, message, data) {
263
+ if (!this.debugOptions.enabled)
264
+ return;
265
+ const levels = ["debug", "info", "warn", "error"];
266
+ const minLevel = this.debugOptions.logLevel || "info";
267
+ if (levels.indexOf(level) < levels.indexOf(minLevel))
268
+ return;
269
+ const entry = {
270
+ timestamp: new Date(),
271
+ level,
272
+ message,
273
+ data: this.debugOptions.includeMemoryUsage
274
+ ? { ...data, memory: this.getMemoryStats() }
275
+ : data,
276
+ };
277
+ // Store log entry
278
+ this.debugLogs.push(entry);
279
+ if (this.debugLogs.length > (this.debugOptions.maxLogEntries || 1000)) {
280
+ this.debugLogs.shift();
281
+ }
282
+ // Call debug callback if provided
283
+ if (this.debugOptions.onDebug) {
284
+ this.debugOptions.onDebug(entry);
285
+ }
286
+ // Log to console if enabled
287
+ if (this.debugOptions.logToConsole) {
288
+ const consoleFn = level === "error"
289
+ ? console.error
290
+ : level === "warn"
291
+ ? console.warn
292
+ : level === "debug"
293
+ ? console.debug
294
+ : console.log;
295
+ consoleFn(`[JwwParser:${level}] ${message}`, data || "");
296
+ }
297
+ }
298
+ // ===========================================================================
299
+ // Initialization Methods
300
+ // ===========================================================================
301
+ /**
302
+ * Check if the parser is initialized
303
+ */
304
+ isInitialized() {
305
+ return this.initialized;
306
+ }
307
+ /**
308
+ * Initialize the WASM module
309
+ * Must be called before using parse methods
310
+ */
311
+ async init() {
312
+ if (this.initialized)
313
+ return;
314
+ if (this.initPromise)
315
+ return this.initPromise;
316
+ this.log("info", "Initializing WASM module");
317
+ const startTime = Date.now();
318
+ this.initPromise = this.loadWasm();
319
+ try {
320
+ await this.initPromise;
321
+ this.initialized = true;
322
+ this.log("info", "WASM module initialized", {
323
+ elapsedMs: Date.now() - startTime,
324
+ });
325
+ }
326
+ catch (error) {
327
+ this.initPromise = null;
328
+ this.log("error", "WASM initialization failed", {
329
+ error: error instanceof Error ? error.message : String(error),
330
+ });
331
+ throw error;
332
+ }
333
+ }
334
+ async loadWasm() {
335
+ // Load wasm_exec.js if Go is not defined
336
+ if (typeof Go === "undefined") {
337
+ await this.loadWasmExec();
338
+ }
339
+ this.goInstance = new Go();
340
+ try {
341
+ if (typeof process !== "undefined" && process.versions?.node) {
342
+ // Node.js environment
343
+ const fs = await import("fs");
344
+ const wasmBuffer = fs.readFileSync(this.wasmPath);
345
+ const wasmModule = await WebAssembly.compile(wasmBuffer);
346
+ this.wasmInstance = await WebAssembly.instantiate(wasmModule, this.goInstance.importObject);
347
+ }
348
+ else {
349
+ // Browser environment
350
+ const result = await WebAssembly.instantiateStreaming(fetch(this.wasmPath), this.goInstance.importObject).catch(async () => {
351
+ const response = await fetch(this.wasmPath);
352
+ if (!response.ok) {
353
+ throw new WasmLoadError(`Failed to fetch WASM file: ${response.status} ${response.statusText}`);
354
+ }
355
+ const bytes = await response.arrayBuffer();
356
+ return WebAssembly.instantiate(bytes, this.goInstance.importObject);
357
+ });
358
+ this.wasmInstance = result.instance;
359
+ }
360
+ }
361
+ catch (error) {
362
+ throw new WasmLoadError(`Failed to load WASM module: ${error instanceof Error ? error.message : String(error)}`, error instanceof Error ? error : undefined);
363
+ }
364
+ // Don't await - Go.run() blocks until the program exits
365
+ this.goInstance.run(this.wasmInstance);
366
+ // Wait for functions to be available
367
+ await this.waitForWasmFunctions();
368
+ // Set debug mode if already enabled
369
+ if (this.debugOptions.enabled &&
370
+ typeof globalThis.jwwSetDebug === "function") {
371
+ globalThis.jwwSetDebug(true);
372
+ }
373
+ }
374
+ async loadWasmExec() {
375
+ if (typeof process !== "undefined" && process.versions?.node) {
376
+ const wasmExecPath = new URL("../wasm/wasm_exec.js", import.meta.url)
377
+ .pathname;
378
+ await import(wasmExecPath);
379
+ }
380
+ else {
381
+ throw new WasmLoadError("Go runtime not loaded. Please include wasm_exec.js in your HTML before using the parser.");
382
+ }
383
+ }
384
+ async waitForWasmFunctions(timeout = 5000, interval = 50) {
385
+ const start = Date.now();
386
+ while (Date.now() - start < timeout) {
387
+ if (typeof globalThis.jwwParse === "function" &&
388
+ typeof globalThis.jwwToDxf === "function" &&
389
+ typeof globalThis.jwwToDxfString === "function") {
390
+ return;
391
+ }
392
+ await new Promise((resolve) => setTimeout(resolve, interval));
393
+ }
394
+ throw new JwwParserError(JwwErrorCode.WASM_TIMEOUT, "WASM functions not available after timeout. The WASM module may have failed to initialize.");
395
+ }
396
+ ensureInitialized() {
397
+ if (!this.initialized) {
398
+ throw new NotInitializedError();
399
+ }
400
+ }
401
+ // ===========================================================================
402
+ // Validation Methods
403
+ // ===========================================================================
404
+ /**
405
+ * Validate a JWW file without fully parsing it
406
+ * Useful for checking file validity before processing
407
+ *
408
+ * @param data - JWW file content as Uint8Array
409
+ * @returns Validation result with any issues found
410
+ */
411
+ validate(data) {
412
+ const startTime = Date.now();
413
+ // Basic validation without WASM (always available)
414
+ const issues = [];
415
+ // Check minimum size
416
+ if (data.length < 16) {
417
+ issues.push({
418
+ severity: "error",
419
+ code: "FILE_TOO_SMALL",
420
+ message: "File is too small to be a valid JWW file",
421
+ details: { size: data.length, minimumRequired: 16 },
422
+ });
423
+ }
424
+ // Check JWW signature "JwwData."
425
+ const signature = new TextDecoder().decode(data.slice(0, 8));
426
+ if (signature !== "JwwData.") {
427
+ issues.push({
428
+ severity: "error",
429
+ code: "INVALID_SIGNATURE",
430
+ message: `Invalid file signature: expected "JwwData.", got "${signature.replace(/[^\x20-\x7E]/g, "?")}"`,
431
+ offset: 0,
432
+ });
433
+ }
434
+ // Try to read version (bytes 8-11, little-endian)
435
+ let version;
436
+ if (data.length >= 12) {
437
+ const view = new DataView(data.buffer, data.byteOffset, data.byteLength);
438
+ version = view.getInt32(8, true);
439
+ if (version < 200 || version > 1000) {
440
+ issues.push({
441
+ severity: "warning",
442
+ code: "UNUSUAL_VERSION",
443
+ message: `Unusual version number: ${version}. Expected between 200-1000.`,
444
+ details: { version },
445
+ });
446
+ }
447
+ }
448
+ // Estimate entity count based on file size (rough heuristic)
449
+ // Average entity is approximately 50-100 bytes
450
+ const estimatedEntityCount = Math.floor(data.length / 75);
451
+ // Size warnings
452
+ const sizeCategory = getSizeCategory(data.length);
453
+ if (sizeCategory === "very_large") {
454
+ issues.push({
455
+ severity: "warning",
456
+ code: "LARGE_FILE",
457
+ message: `File is very large (${formatBytes(data.length)}). Consider using streaming mode for better performance.`,
458
+ details: { size: data.length, sizeFormatted: formatBytes(data.length) },
459
+ });
460
+ }
461
+ const hasErrors = issues.some((i) => i.severity === "error");
462
+ return {
463
+ valid: !hasErrors,
464
+ version,
465
+ sizeCategory,
466
+ estimatedEntityCount,
467
+ issues,
468
+ validationTimeMs: Date.now() - startTime,
469
+ };
470
+ }
471
+ /**
472
+ * Validate and throw if invalid
473
+ * Convenience method that throws a ValidationError if the file is invalid
474
+ *
475
+ * @param data - JWW file content as Uint8Array
476
+ * @throws {ValidationError} If the file is invalid
477
+ */
478
+ validateOrThrow(data) {
479
+ const result = this.validate(data);
480
+ if (!result.valid) {
481
+ throw new ValidationError("File validation failed: " +
482
+ result.issues
483
+ .filter((i) => i.severity === "error")
484
+ .map((i) => i.message)
485
+ .join("; "), result.issues);
486
+ }
487
+ }
488
+ // ===========================================================================
489
+ // Parsing Methods
490
+ // ===========================================================================
491
+ /**
492
+ * Parse a JWW file and return the document structure
493
+ *
494
+ * @param data - JWW file content as Uint8Array
495
+ * @param options - Optional parsing options
496
+ * @returns Parsed JWW document
497
+ * @throws {NotInitializedError} If parser is not initialized
498
+ * @throws {ParseError} If parsing fails
499
+ */
500
+ parse(data, options) {
501
+ this.ensureInitialized();
502
+ // Check for abort signal
503
+ if (options?.signal?.aborted) {
504
+ throw new JwwParserError(JwwErrorCode.PARSE_ERROR, "Parse operation was aborted");
505
+ }
506
+ const startTime = Date.now();
507
+ this.log("info", "Starting parse operation", {
508
+ dataSize: data.length,
509
+ options: options ? { ...options, onProgress: undefined } : undefined,
585
510
  });
586
- }
587
- const doc = JSON.parse(result.data);
588
- const parseTime = Date.now() - startTime;
589
- this.stats.parseCount++;
590
- this.stats.totalBytesProcessed += data.length;
591
- this.stats.parseTimes.push(parseTime);
592
- this.log("info", "DXF conversion complete", {
593
- parseTimeMs: parseTime,
594
- entityCount: doc.Entities.length
595
- });
596
- return doc;
597
- } catch (error) {
598
- this.stats.errorCount++;
599
- this.log("error", "DXF conversion failed", {
600
- error: error instanceof Error ? error.message : String(error)
601
- });
602
- if (error instanceof JwwParserError) {
603
- throw error;
604
- }
605
- throw new ParseError(
606
- error instanceof Error ? error.message : String(error),
607
- { cause: error instanceof Error ? error : void 0 }
608
- );
609
- }
610
- }
611
- /**
612
- * Parse a JWW file and convert to DXF file content string
613
- *
614
- * @param data - JWW file content as Uint8Array
615
- * @param options - Optional conversion options
616
- * @returns DXF file content as string (ready to save as .dxf file)
617
- */
618
- toDxfString(data, options) {
619
- this.ensureInitialized();
620
- const startTime = Date.now();
621
- this.log("info", "Starting DXF string generation", { dataSize: data.length });
622
- try {
623
- const result = globalThis.jwwToDxfString(data);
624
- if (!result.ok) {
625
- this.stats.errorCount++;
626
- throw createParseError(result);
627
- }
628
- const parseTime = Date.now() - startTime;
629
- this.stats.parseCount++;
630
- this.stats.totalBytesProcessed += data.length;
631
- this.stats.parseTimes.push(parseTime);
632
- this.log("info", "DXF string generation complete", {
633
- parseTimeMs: parseTime,
634
- outputLength: result.data.length
635
- });
636
- return result.data;
637
- } catch (error) {
638
- this.stats.errorCount++;
639
- if (error instanceof JwwParserError) {
640
- throw error;
641
- }
642
- throw new ParseError(
643
- error instanceof Error ? error.message : String(error),
644
- { cause: error instanceof Error ? error : void 0 }
645
- );
646
- }
647
- }
648
- // ===========================================================================
649
- // Memory Management
650
- // ===========================================================================
651
- /**
652
- * Get current memory usage statistics
653
- */
654
- getMemoryStats() {
655
- let wasmMemoryBytes = 0;
656
- if (this.wasmInstance?.exports.memory) {
657
- const memory = this.wasmInstance.exports.memory;
658
- wasmMemoryBytes = memory.buffer.byteLength;
659
- }
660
- let jsHeapBytes;
661
- if (typeof process !== "undefined" && typeof process.memoryUsage === "function") {
662
- jsHeapBytes = process.memoryUsage().heapUsed;
663
- }
664
- const totalBytes = wasmMemoryBytes + (jsHeapBytes || 0);
665
- return {
666
- wasmMemoryBytes,
667
- jsHeapBytes,
668
- totalBytes,
669
- totalFormatted: formatBytes(totalBytes)
670
- };
671
- }
672
- /**
673
- * Get parser statistics
674
- */
675
- getStats() {
676
- const parseTimes = this.stats.parseTimes;
677
- return {
678
- parseCount: this.stats.parseCount,
679
- totalBytesProcessed: this.stats.totalBytesProcessed,
680
- averageParseTimeMs: parseTimes.length > 0 ? parseTimes.reduce((a, b) => a + b, 0) / parseTimes.length : 0,
681
- fastestParseTimeMs: parseTimes.length > 0 ? Math.min(...parseTimes) : 0,
682
- slowestParseTimeMs: parseTimes.length > 0 ? Math.max(...parseTimes) : 0,
683
- errorCount: this.stats.errorCount,
684
- memoryStats: this.getMemoryStats()
685
- };
686
- }
687
- /**
688
- * Reset parser statistics
689
- */
690
- resetStats() {
691
- this.stats = {
692
- parseCount: 0,
693
- totalBytesProcessed: 0,
694
- parseTimes: [],
695
- errorCount: 0
696
- };
697
- this.log("debug", "Statistics reset");
698
- }
699
- /**
700
- * Clean up resources and release memory
701
- * Call this when you're done using the parser to free WASM memory
702
- */
703
- dispose() {
704
- this.log("info", "Disposing parser resources");
705
- this.goInstance = null;
706
- this.wasmInstance = null;
707
- this.initialized = false;
708
- this.initPromise = null;
709
- this.stats = {
710
- parseCount: 0,
711
- totalBytesProcessed: 0,
712
- parseTimes: [],
713
- errorCount: 0
714
- };
715
- this.debugLogs = [];
716
- }
717
- /**
718
- * Get the WASM module version
719
- */
720
- getVersion() {
721
- if (typeof globalThis.jwwGetVersion === "function") {
722
- return globalThis.jwwGetVersion();
723
- }
724
- return "1.0.0";
725
- }
726
- };
727
- async function createParser(wasmPath, options) {
728
- const parser = new JwwParser(wasmPath);
729
- if (options?.debug) {
730
- parser.setDebug(options.debug);
731
- }
732
- await parser.init();
733
- return parser;
511
+ // Report initial progress
512
+ if (options?.onProgress) {
513
+ options.onProgress({
514
+ stage: "loading",
515
+ progress: 0,
516
+ overallProgress: 0,
517
+ message: "Loading file data",
518
+ elapsedMs: 0,
519
+ });
520
+ }
521
+ try {
522
+ const result = globalThis.jwwParse(data);
523
+ if (!result.ok) {
524
+ this.stats.errorCount++;
525
+ throw createParseError(result);
526
+ }
527
+ // Report parsing complete
528
+ if (options?.onProgress) {
529
+ options.onProgress({
530
+ stage: "complete",
531
+ progress: 100,
532
+ overallProgress: 100,
533
+ message: "Parsing complete",
534
+ elapsedMs: Date.now() - startTime,
535
+ });
536
+ }
537
+ const doc = JSON.parse(result.data);
538
+ // Apply filtering options
539
+ let filteredDoc = doc;
540
+ if (options) {
541
+ filteredDoc = this.applyParseOptions(doc, options);
542
+ }
543
+ // Update stats
544
+ const parseTime = Date.now() - startTime;
545
+ this.stats.parseCount++;
546
+ this.stats.totalBytesProcessed += data.length;
547
+ this.stats.parseTimes.push(parseTime);
548
+ if (this.stats.parseTimes.length > 100) {
549
+ this.stats.parseTimes.shift();
550
+ }
551
+ this.log("info", "Parse complete", {
552
+ parseTimeMs: parseTime,
553
+ entityCount: filteredDoc.Entities.length,
554
+ blockCount: filteredDoc.Blocks.length,
555
+ });
556
+ return filteredDoc;
557
+ }
558
+ catch (error) {
559
+ this.stats.errorCount++;
560
+ this.log("error", "Parse failed", {
561
+ error: error instanceof Error ? error.message : String(error),
562
+ });
563
+ if (error instanceof JwwParserError) {
564
+ throw error;
565
+ }
566
+ throw new ParseError(error instanceof Error ? error.message : String(error), { cause: error instanceof Error ? error : undefined });
567
+ }
568
+ }
569
+ applyParseOptions(doc, options) {
570
+ let entities = doc.Entities;
571
+ let blocks = doc.Blocks;
572
+ // Filter by entity type
573
+ if (options.skipEntityTypes && options.skipEntityTypes.length > 0) {
574
+ const skipTypes = new Set(options.skipEntityTypes);
575
+ entities = entities.filter((e) => !skipTypes.has(e.Type));
576
+ }
577
+ // Filter by layer group
578
+ if (options.layerGroupFilter && options.layerGroupFilter.length > 0) {
579
+ const groups = new Set(options.layerGroupFilter);
580
+ entities = entities.filter((e) => groups.has(e.LayerGroup));
581
+ }
582
+ // Filter by specific layers
583
+ if (options.layerFilter) {
584
+ const filter = options.layerFilter;
585
+ entities = entities.filter((e) => {
586
+ const layers = filter[e.LayerGroup];
587
+ return !layers || layers.includes(e.Layer);
588
+ });
589
+ }
590
+ // Limit entity count
591
+ if (options.maxEntities && entities.length > options.maxEntities) {
592
+ entities = entities.slice(0, options.maxEntities);
593
+ }
594
+ // Handle blocks
595
+ if (options.includeBlocks === false) {
596
+ blocks = [];
597
+ }
598
+ return {
599
+ ...doc,
600
+ Entities: entities,
601
+ Blocks: blocks,
602
+ };
603
+ }
604
+ /**
605
+ * Parse a JWW file and convert to DXF document structure
606
+ *
607
+ * @param data - JWW file content as Uint8Array
608
+ * @param options - Optional conversion options
609
+ * @returns DXF document object
610
+ */
611
+ toDxf(data, options) {
612
+ this.ensureInitialized();
613
+ const startTime = Date.now();
614
+ this.log("info", "Starting DXF conversion", { dataSize: data.length });
615
+ if (options?.onProgress) {
616
+ options.onProgress({
617
+ stage: "converting",
618
+ progress: 0,
619
+ overallProgress: 0,
620
+ message: "Converting to DXF",
621
+ elapsedMs: 0,
622
+ });
623
+ }
624
+ try {
625
+ const result = globalThis.jwwToDxf(data);
626
+ if (!result.ok) {
627
+ this.stats.errorCount++;
628
+ throw createParseError(result);
629
+ }
630
+ if (options?.onProgress) {
631
+ options.onProgress({
632
+ stage: "complete",
633
+ progress: 100,
634
+ overallProgress: 100,
635
+ message: "Conversion complete",
636
+ elapsedMs: Date.now() - startTime,
637
+ });
638
+ }
639
+ const doc = JSON.parse(result.data);
640
+ // Update stats
641
+ const parseTime = Date.now() - startTime;
642
+ this.stats.parseCount++;
643
+ this.stats.totalBytesProcessed += data.length;
644
+ this.stats.parseTimes.push(parseTime);
645
+ this.log("info", "DXF conversion complete", {
646
+ parseTimeMs: parseTime,
647
+ entityCount: doc.Entities.length,
648
+ });
649
+ return doc;
650
+ }
651
+ catch (error) {
652
+ this.stats.errorCount++;
653
+ this.log("error", "DXF conversion failed", {
654
+ error: error instanceof Error ? error.message : String(error),
655
+ });
656
+ if (error instanceof JwwParserError) {
657
+ throw error;
658
+ }
659
+ throw new ParseError(error instanceof Error ? error.message : String(error), { cause: error instanceof Error ? error : undefined });
660
+ }
661
+ }
662
+ /**
663
+ * Parse a JWW file and convert to DXF file content string
664
+ *
665
+ * @param data - JWW file content as Uint8Array
666
+ * @param options - Optional conversion options
667
+ * @returns DXF file content as string (ready to save as .dxf file)
668
+ */
669
+ toDxfString(data, options) {
670
+ this.ensureInitialized();
671
+ const startTime = Date.now();
672
+ this.log("info", "Starting DXF string generation", { dataSize: data.length });
673
+ try {
674
+ const result = globalThis.jwwToDxfString(data);
675
+ if (!result.ok) {
676
+ this.stats.errorCount++;
677
+ throw createParseError(result);
678
+ }
679
+ const parseTime = Date.now() - startTime;
680
+ this.stats.parseCount++;
681
+ this.stats.totalBytesProcessed += data.length;
682
+ this.stats.parseTimes.push(parseTime);
683
+ this.log("info", "DXF string generation complete", {
684
+ parseTimeMs: parseTime,
685
+ outputLength: result.data.length,
686
+ });
687
+ return result.data;
688
+ }
689
+ catch (error) {
690
+ this.stats.errorCount++;
691
+ if (error instanceof JwwParserError) {
692
+ throw error;
693
+ }
694
+ throw new ParseError(error instanceof Error ? error.message : String(error), { cause: error instanceof Error ? error : undefined });
695
+ }
696
+ }
697
+ // ===========================================================================
698
+ // Memory Management
699
+ // ===========================================================================
700
+ /**
701
+ * Get current memory usage statistics
702
+ */
703
+ getMemoryStats() {
704
+ let wasmMemoryBytes = 0;
705
+ // Try to get WASM memory size
706
+ if (this.wasmInstance?.exports.memory) {
707
+ const memory = this.wasmInstance.exports.memory;
708
+ wasmMemoryBytes = memory.buffer.byteLength;
709
+ }
710
+ // Try to get JS heap size (Node.js only)
711
+ let jsHeapBytes;
712
+ if (typeof process !== "undefined" &&
713
+ typeof process.memoryUsage === "function") {
714
+ jsHeapBytes = process.memoryUsage().heapUsed;
715
+ }
716
+ const totalBytes = wasmMemoryBytes + (jsHeapBytes || 0);
717
+ return {
718
+ wasmMemoryBytes,
719
+ jsHeapBytes,
720
+ totalBytes,
721
+ totalFormatted: formatBytes(totalBytes),
722
+ };
723
+ }
724
+ /**
725
+ * Get parser statistics
726
+ */
727
+ getStats() {
728
+ const parseTimes = this.stats.parseTimes;
729
+ return {
730
+ parseCount: this.stats.parseCount,
731
+ totalBytesProcessed: this.stats.totalBytesProcessed,
732
+ averageParseTimeMs: parseTimes.length > 0
733
+ ? parseTimes.reduce((a, b) => a + b, 0) / parseTimes.length
734
+ : 0,
735
+ fastestParseTimeMs: parseTimes.length > 0 ? Math.min(...parseTimes) : 0,
736
+ slowestParseTimeMs: parseTimes.length > 0 ? Math.max(...parseTimes) : 0,
737
+ errorCount: this.stats.errorCount,
738
+ memoryStats: this.getMemoryStats(),
739
+ };
740
+ }
741
+ /**
742
+ * Reset parser statistics
743
+ */
744
+ resetStats() {
745
+ this.stats = {
746
+ parseCount: 0,
747
+ totalBytesProcessed: 0,
748
+ parseTimes: [],
749
+ errorCount: 0,
750
+ };
751
+ this.log("debug", "Statistics reset");
752
+ }
753
+ /**
754
+ * Clean up resources and release memory
755
+ * Call this when you're done using the parser to free WASM memory
756
+ */
757
+ dispose() {
758
+ this.log("info", "Disposing parser resources");
759
+ // Clear references
760
+ this.goInstance = null;
761
+ this.wasmInstance = null;
762
+ this.initialized = false;
763
+ this.initPromise = null;
764
+ // Clear stats and logs
765
+ this.stats = {
766
+ parseCount: 0,
767
+ totalBytesProcessed: 0,
768
+ parseTimes: [],
769
+ errorCount: 0,
770
+ };
771
+ this.debugLogs = [];
772
+ // Note: We cannot truly "free" WASM memory, but clearing references
773
+ // allows garbage collection to reclaim the memory
774
+ }
775
+ /**
776
+ * Get the WASM module version
777
+ */
778
+ getVersion() {
779
+ if (typeof globalThis.jwwGetVersion === "function") {
780
+ return globalThis.jwwGetVersion();
781
+ }
782
+ return "1.0.0"; // Fallback version
783
+ }
734
784
  }
735
- function quickValidate(data) {
736
- const parser = new JwwParser();
737
- return parser.validate(data);
785
+ // =============================================================================
786
+ // Factory Functions
787
+ // =============================================================================
788
+ /**
789
+ * Create and initialize a JWW parser instance
790
+ *
791
+ * @param wasmPath - Optional path to the jww-parser.wasm file
792
+ * @param options - Optional debug options
793
+ * @returns Initialized JwwParser instance
794
+ *
795
+ * @example
796
+ * ```typescript
797
+ * const parser = await createParser();
798
+ * const doc = parser.parse(fileData);
799
+ * ```
800
+ */
801
+ export async function createParser(wasmPath, options) {
802
+ const parser = new JwwParser(wasmPath);
803
+ if (options?.debug) {
804
+ parser.setDebug(options.debug);
805
+ }
806
+ await parser.init();
807
+ return parser;
738
808
  }
739
- function isJwwFile(data) {
740
- if (data.length < 8) return false;
741
- const signature = new TextDecoder().decode(data.slice(0, 8));
742
- return signature === "JwwData.";
809
+ /**
810
+ * Quick validate a JWW file without initializing a full parser
811
+ * Performs basic validation only (signature, version check)
812
+ *
813
+ * @param data - JWW file content as Uint8Array
814
+ * @returns Validation result
815
+ */
816
+ export function quickValidate(data) {
817
+ const parser = new JwwParser();
818
+ return parser.validate(data);
819
+ }
820
+ /**
821
+ * Check if a Uint8Array looks like a JWW file
822
+ *
823
+ * @param data - File content as Uint8Array
824
+ * @returns true if the file appears to be a JWW file
825
+ */
826
+ export function isJwwFile(data) {
827
+ if (data.length < 8)
828
+ return false;
829
+ const signature = new TextDecoder().decode(data.slice(0, 8));
830
+ return signature === "JwwData.";
743
831
  }
744
- var index_default = {
745
- JwwParser,
746
- createParser,
747
- quickValidate,
748
- isJwwFile,
749
- JwwParserError,
750
- NotInitializedError,
751
- WasmLoadError,
752
- ValidationError,
753
- ParseError,
754
- JwwErrorCode
832
+ // =============================================================================
833
+ // Default Export
834
+ // =============================================================================
835
+ export default {
836
+ JwwParser,
837
+ createParser,
838
+ quickValidate,
839
+ isJwwFile,
840
+ JwwParserError,
841
+ NotInitializedError,
842
+ WasmLoadError,
843
+ ValidationError,
844
+ ParseError,
845
+ JwwErrorCode,
755
846
  };
756
- // Annotate the CommonJS export names for ESM import in node:
757
- 0 && (module.exports = {
758
- JwwErrorCode,
759
- JwwParser,
760
- JwwParserError,
761
- NotInitializedError,
762
- ParseError,
763
- ValidationError,
764
- WasmLoadError,
765
- createParser,
766
- isJwwFile,
767
- quickValidate
768
- });