@willwade/aac-processors 0.1.4 → 0.1.6

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (47) hide show
  1. package/README.md +14 -0
  2. package/dist/browser/index.browser.js +15 -1
  3. package/dist/browser/processors/gridset/password.js +11 -0
  4. package/dist/browser/processors/gridset/symbols.js +3 -2
  5. package/dist/browser/processors/gridsetProcessor.js +42 -46
  6. package/dist/browser/processors/obfProcessor.js +47 -63
  7. package/dist/browser/processors/snapProcessor.js +1031 -0
  8. package/dist/browser/processors/touchchatProcessor.js +1004 -0
  9. package/dist/browser/utils/io.js +21 -1
  10. package/dist/browser/utils/sqlite.js +109 -0
  11. package/dist/browser/utils/zip.js +54 -0
  12. package/dist/browser/validation/gridsetValidator.js +21 -2
  13. package/dist/browser/validation/obfValidator.js +4 -5
  14. package/dist/browser/validation/snapValidator.js +200 -0
  15. package/dist/browser/validation/touchChatValidator.js +202 -0
  16. package/dist/index.browser.d.ts +7 -0
  17. package/dist/index.browser.js +19 -2
  18. package/dist/processors/gridset/helpers.js +3 -4
  19. package/dist/processors/gridset/index.d.ts +1 -1
  20. package/dist/processors/gridset/index.js +3 -2
  21. package/dist/processors/gridset/password.d.ts +3 -2
  22. package/dist/processors/gridset/password.js +12 -0
  23. package/dist/processors/gridset/symbols.js +2 -1
  24. package/dist/processors/gridset/wordlistHelpers.js +107 -51
  25. package/dist/processors/gridsetProcessor.js +40 -44
  26. package/dist/processors/obfProcessor.js +46 -62
  27. package/dist/processors/snapProcessor.js +60 -54
  28. package/dist/processors/touchchatProcessor.js +38 -36
  29. package/dist/utils/io.d.ts +5 -0
  30. package/dist/utils/io.js +23 -0
  31. package/dist/utils/sqlite.d.ts +21 -0
  32. package/dist/utils/sqlite.js +137 -0
  33. package/dist/utils/zip.d.ts +7 -0
  34. package/dist/utils/zip.js +80 -0
  35. package/dist/validation/gridsetValidator.js +20 -24
  36. package/dist/validation/obfValidator.js +4 -28
  37. package/docs/BROWSER_USAGE.md +2 -10
  38. package/examples/README.md +3 -75
  39. package/examples/vitedemo/README.md +17 -10
  40. package/examples/vitedemo/index.html +2 -2
  41. package/examples/vitedemo/package-lock.json +531 -1
  42. package/examples/vitedemo/package.json +7 -1
  43. package/examples/vitedemo/src/main.ts +48 -2
  44. package/examples/vitedemo/src/vite-env.d.ts +1 -0
  45. package/package.json +3 -1
  46. package/examples/browser-test-server.js +0 -81
  47. package/examples/vitedemo/QUICKSTART.md +0 -75
@@ -29,31 +29,7 @@ const treeStructure_1 = require("../core/treeStructure");
29
29
  const idGenerator_1 = require("../utilities/analytics/utils/idGenerator");
30
30
  const translationProcessor_1 = require("../utilities/translation/translationProcessor");
31
31
  const io_1 = require("../utils/io");
32
- let JSZipModuleObf;
33
- async function getJSZipObf() {
34
- if (!JSZipModuleObf) {
35
- try {
36
- // Try ES module import first (browser/Vite)
37
- const module = await Promise.resolve().then(() => __importStar(require('jszip')));
38
- JSZipModuleObf = module.default || module;
39
- }
40
- catch (error) {
41
- // Fall back to CommonJS require (Node.js)
42
- try {
43
- // eslint-disable-next-line @typescript-eslint/no-var-requires
44
- const module = require('jszip');
45
- JSZipModuleObf = module.default || module;
46
- }
47
- catch (err2) {
48
- throw new Error('Zip handling requires JSZip in this environment.');
49
- }
50
- }
51
- }
52
- if (!JSZipModuleObf) {
53
- throw new Error('Zip handling requires JSZip in this environment.');
54
- }
55
- return JSZipModuleObf;
56
- }
32
+ const zip_1 = require("../utils/zip");
57
33
  const OBF_FORMAT_VERSION = 'open-board-0.1';
58
34
  /**
59
35
  * Map OBF hidden value to AAC standard visibility
@@ -91,10 +67,12 @@ class ObfProcessor extends baseProcessor_1.BaseProcessor {
91
67
  ].filter(Boolean);
92
68
  for (const imagePath of possiblePaths) {
93
69
  try {
94
- const file = this.zipFile.file(imagePath);
95
- if (file) {
96
- const buffer = await file.async('nodebuffer');
97
- return buffer;
70
+ const buffer = await this.zipFile.readFile(imagePath);
71
+ if (buffer) {
72
+ if (typeof Buffer !== 'undefined') {
73
+ return Buffer.from(buffer);
74
+ }
75
+ return null;
98
76
  }
99
77
  }
100
78
  catch (err) {
@@ -128,9 +106,8 @@ class ObfProcessor extends baseProcessor_1.BaseProcessor {
128
106
  ].filter(Boolean);
129
107
  for (const imagePath of possiblePaths) {
130
108
  try {
131
- const file = this.zipFile.file(imagePath);
132
- if (file) {
133
- const buffer = await file.async('uint8array');
109
+ const buffer = await this.zipFile.readFile(imagePath);
110
+ if (buffer) {
134
111
  const contentType = imageData.content_type ||
135
112
  this.getMimeTypeFromFilename(imagePath);
136
113
  const dataUrl = `data:${contentType};base64,${(0, io_1.encodeBase64)(buffer)}`;
@@ -433,36 +410,28 @@ class ObfProcessor extends baseProcessor_1.BaseProcessor {
433
410
  if (!isLikelyZip(filePathOrBuffer)) {
434
411
  throw new Error('Invalid OBF content: not JSON and not ZIP');
435
412
  }
436
- const JSZip = await getJSZipObf();
437
- let zip;
438
413
  try {
439
- const zipInput = (0, io_1.readBinaryFromInput)(filePathOrBuffer);
440
- zip = await JSZip.loadAsync(zipInput);
414
+ const zipResult = await (0, zip_1.openZipFromInput)(filePathOrBuffer);
415
+ this.zipFile = zipResult.zip;
441
416
  }
442
417
  catch (err) {
443
- console.error('[OBF] Error loading ZIP with JSZip:', err);
418
+ console.error('[OBF] Error loading ZIP:', err);
444
419
  throw err;
445
420
  }
446
421
  // Store the ZIP file reference for image extraction
447
- this.zipFile = zip;
448
422
  this.imageCache.clear(); // Clear cache for new file
449
423
  console.log('[OBF] Detected zip archive, extracting .obf files');
450
424
  // Collect all .obf entries
451
- const obfEntries = [];
452
- zip.forEach((relativePath, file) => {
453
- if (file.dir)
454
- return;
455
- if (relativePath.toLowerCase().endsWith('.obf')) {
456
- obfEntries.push({ name: relativePath, file });
457
- }
458
- });
425
+ const obfEntries = this.zipFile
426
+ .listFiles()
427
+ .filter((name) => name.toLowerCase().endsWith('.obf'));
459
428
  // Process each .obf entry
460
- for (const entry of obfEntries) {
429
+ for (const entryName of obfEntries) {
461
430
  try {
462
- const content = await entry.file.async('string');
463
- const boardData = tryParseObfJson(content);
431
+ const content = await this.zipFile.readFile(entryName);
432
+ const boardData = tryParseObfJson((0, io_1.decodeText)(content));
464
433
  if (boardData) {
465
- const page = await this.processBoard(boardData, entry.name);
434
+ const page = await this.processBoard(boardData, entryName);
466
435
  tree.addPage(page);
467
436
  // Set metadata if not already set (use first board as reference)
468
437
  if (!tree.metadata.format) {
@@ -479,11 +448,11 @@ class ObfProcessor extends baseProcessor_1.BaseProcessor {
479
448
  }
480
449
  }
481
450
  else {
482
- console.warn('[OBF] Skipped entry (not valid OBF JSON):', entry.name);
451
+ console.warn('[OBF] Skipped entry (not valid OBF JSON):', entryName);
483
452
  }
484
453
  }
485
454
  catch (err) {
486
- console.warn('[OBF] Error processing entry:', entry.name, err);
455
+ console.warn('[OBF] Error processing entry:', entryName, err);
487
456
  }
488
457
  }
489
458
  return tree;
@@ -609,16 +578,31 @@ class ObfProcessor extends baseProcessor_1.BaseProcessor {
609
578
  }
610
579
  else {
611
580
  // Save as OBZ (zip with multiple OBF files)
612
- const JSZip = await getJSZipObf();
613
- const zip = new JSZip();
614
- Object.values(tree.pages).forEach((page) => {
615
- const obfBoard = this.createObfBoardFromPage(page, 'Board', tree.metadata);
616
- const obfContent = JSON.stringify(obfBoard, null, 2);
617
- zip.file(`${page.id}.obf`, obfContent);
618
- });
619
- const zipBuffer = await zip.generateAsync({ type: 'uint8array' });
620
- const { writeBinaryToPath } = await Promise.resolve().then(() => __importStar(require('../utils/io')));
621
- writeBinaryToPath(outputPath, zipBuffer);
581
+ if ((0, io_1.isNodeRuntime)()) {
582
+ const AdmZip = (0, io_1.getNodeRequire)()('adm-zip');
583
+ const zip = new AdmZip();
584
+ Object.values(tree.pages).forEach((page) => {
585
+ const obfBoard = this.createObfBoardFromPage(page, 'Board', tree.metadata);
586
+ const obfContent = JSON.stringify(obfBoard, null, 2);
587
+ zip.addFile(`${page.id}.obf`, Buffer.from(obfContent, 'utf8'));
588
+ });
589
+ const zipBuffer = zip.toBuffer();
590
+ const { writeBinaryToPath } = await Promise.resolve().then(() => __importStar(require('../utils/io')));
591
+ writeBinaryToPath(outputPath, zipBuffer);
592
+ }
593
+ else {
594
+ const module = await Promise.resolve().then(() => __importStar(require('jszip')));
595
+ const JSZip = module.default || module;
596
+ const zip = new JSZip();
597
+ Object.values(tree.pages).forEach((page) => {
598
+ const obfBoard = this.createObfBoardFromPage(page, 'Board', tree.metadata);
599
+ const obfContent = JSON.stringify(obfBoard, null, 2);
600
+ zip.file(`${page.id}.obf`, obfContent);
601
+ });
602
+ const zipBuffer = await zip.generateAsync({ type: 'uint8array' });
603
+ const { writeBinaryToPath } = await Promise.resolve().then(() => __importStar(require('../utils/io')));
604
+ writeBinaryToPath(outputPath, zipBuffer);
605
+ }
622
606
  }
623
607
  }
624
608
  /**
@@ -1,18 +1,12 @@
1
1
  "use strict";
2
- var __importDefault = (this && this.__importDefault) || function (mod) {
3
- return (mod && mod.__esModule) ? mod : { "default": mod };
4
- };
5
2
  Object.defineProperty(exports, "__esModule", { value: true });
6
3
  exports.SnapProcessor = void 0;
7
4
  const baseProcessor_1 = require("../core/baseProcessor");
8
5
  const treeStructure_1 = require("../core/treeStructure");
9
6
  const idGenerator_1 = require("../utilities/analytics/utils/idGenerator");
10
- const better_sqlite3_1 = __importDefault(require("better-sqlite3"));
11
- const path_1 = __importDefault(require("path"));
12
- const fs_1 = __importDefault(require("fs"));
13
- const crypto_1 = __importDefault(require("crypto"));
14
- const os_1 = __importDefault(require("os"));
15
7
  const snapValidator_1 = require("../validation/snapValidator");
8
+ const io_1 = require("../utils/io");
9
+ const sqlite_1 = require("../utils/sqlite");
16
10
  /**
17
11
  * Map Snap Visible value to AAC standard visibility
18
12
  * Snap: 0 = hidden, 1 (or non-zero) = visible
@@ -56,22 +50,10 @@ class SnapProcessor extends baseProcessor_1.BaseProcessor {
56
50
  async loadIntoTree(filePathOrBuffer) {
57
51
  await Promise.resolve();
58
52
  const tree = new treeStructure_1.AACTree();
59
- let tempDir = null;
60
- const filePath = typeof filePathOrBuffer !== 'string'
61
- ? (() => {
62
- tempDir = fs_1.default.mkdtempSync(path_1.default.join(os_1.default.tmpdir(), 'snap-'));
63
- return path_1.default.join(tempDir, 'input.spb');
64
- })()
65
- : filePathOrBuffer;
66
- if (typeof filePathOrBuffer !== 'string') {
67
- const buffer = Buffer.isBuffer(filePathOrBuffer)
68
- ? filePathOrBuffer
69
- : Buffer.from(filePathOrBuffer);
70
- fs_1.default.writeFileSync(filePath, buffer);
71
- }
72
- let db = null;
53
+ let dbResult = null;
73
54
  try {
74
- db = new better_sqlite3_1.default(filePath, { readonly: true });
55
+ dbResult = await (0, sqlite_1.openSqliteDatabase)(filePathOrBuffer, { readonly: true });
56
+ const db = dbResult.db;
75
57
  const getTableColumns = (tableName) => {
76
58
  try {
77
59
  const rows = db.prepare(`PRAGMA table_info(${tableName})`).all();
@@ -338,10 +320,12 @@ class SnapProcessor extends baseProcessor_1.BaseProcessor {
338
320
  ${hasCommandSequence ? 'LEFT JOIN CommandSequence cs ON b.Id = cs.ButtonId' : ''}
339
321
  WHERE er.PageId = ? ${selectedPageLayoutId ? 'AND ep.PageLayoutId = ?' : ''}
340
322
  `;
341
- const queryParams = selectedPageLayoutId
342
- ? [pageRow.Id, selectedPageLayoutId]
343
- : [pageRow.Id];
344
- buttons = db.prepare(buttonQuery).all(...queryParams);
323
+ if (selectedPageLayoutId) {
324
+ buttons = db.prepare(buttonQuery).all(pageRow.Id, selectedPageLayoutId);
325
+ }
326
+ else {
327
+ buttons = db.prepare(buttonQuery).all(pageRow.Id);
328
+ }
345
329
  }
346
330
  catch (err) {
347
331
  const errorMessage = err instanceof Error ? err.message : String(err);
@@ -606,22 +590,18 @@ class SnapProcessor extends baseProcessor_1.BaseProcessor {
606
590
  }
607
591
  }
608
592
  finally {
609
- // Ensure database is closed
610
- if (db) {
611
- db.close();
593
+ if (dbResult?.cleanup) {
594
+ dbResult.cleanup();
612
595
  }
613
- // Clean up temporary file if created from buffer
614
- if (tempDir && fs_1.default.existsSync(tempDir)) {
615
- try {
616
- fs_1.default.rmSync(tempDir, { recursive: true, force: true });
617
- }
618
- catch (e) {
619
- console.warn('Failed to clean up temporary files:', e);
620
- }
596
+ else if (dbResult?.db) {
597
+ dbResult.db.close();
621
598
  }
622
599
  }
623
600
  }
624
601
  async processTexts(filePathOrBuffer, translations, outputPath) {
602
+ if (!(0, io_1.isNodeRuntime)()) {
603
+ throw new Error('processTexts is only supported in Node.js environments for Snap files.');
604
+ }
625
605
  // Load the tree, apply translations, and save to new file
626
606
  const tree = await this.loadIntoTree(filePathOrBuffer);
627
607
  // Apply translations to all text content
@@ -651,19 +631,26 @@ class SnapProcessor extends baseProcessor_1.BaseProcessor {
651
631
  });
652
632
  // Save the translated tree and return its content
653
633
  await this.saveFromTree(tree, outputPath);
654
- return fs_1.default.readFileSync(outputPath);
634
+ const fs = (0, io_1.getFs)();
635
+ return fs.readFileSync(outputPath);
655
636
  }
656
637
  async saveFromTree(tree, outputPath) {
638
+ if (!(0, io_1.isNodeRuntime)()) {
639
+ throw new Error('saveFromTree is only supported in Node.js environments for Snap files.');
640
+ }
657
641
  await Promise.resolve();
658
- const outputDir = path_1.default.dirname(outputPath);
659
- if (!fs_1.default.existsSync(outputDir)) {
660
- fs_1.default.mkdirSync(outputDir, { recursive: true });
642
+ const fs = (0, io_1.getFs)();
643
+ const path = (0, io_1.getPath)();
644
+ const outputDir = path.dirname(outputPath);
645
+ if (!fs.existsSync(outputDir)) {
646
+ fs.mkdirSync(outputDir, { recursive: true });
661
647
  }
662
- if (fs_1.default.existsSync(outputPath)) {
663
- fs_1.default.unlinkSync(outputPath);
648
+ if (fs.existsSync(outputPath)) {
649
+ fs.unlinkSync(outputPath);
664
650
  }
665
651
  // Create a new SQLite database for Snap format
666
- const db = new better_sqlite3_1.default(outputPath, { readonly: false });
652
+ const Database = (0, sqlite_1.requireBetterSqlite3)();
653
+ const db = new Database(outputPath, { readonly: false });
667
654
  try {
668
655
  // Create basic Snap database schema (simplified)
669
656
  db.exec(`
@@ -852,7 +839,12 @@ class SnapProcessor extends baseProcessor_1.BaseProcessor {
852
839
  */
853
840
  async addAudioToButton(dbPath, buttonId, audioData, metadata) {
854
841
  await Promise.resolve();
855
- const db = new better_sqlite3_1.default(dbPath, { fileMustExist: true });
842
+ if (!(0, io_1.isNodeRuntime)()) {
843
+ throw new Error('addAudioToButton is only supported in Node.js environments.');
844
+ }
845
+ const Database = (0, sqlite_1.requireBetterSqlite3)();
846
+ const crypto = (0, io_1.getNodeRequire)()('crypto');
847
+ const db = new Database(dbPath, { fileMustExist: true });
856
848
  try {
857
849
  // Ensure PageSetData table exists
858
850
  db.exec(`
@@ -863,7 +855,7 @@ class SnapProcessor extends baseProcessor_1.BaseProcessor {
863
855
  );
864
856
  `);
865
857
  // Generate SHA1 hash for the identifier
866
- const sha1Hash = crypto_1.default.createHash('sha1').update(audioData).digest('hex');
858
+ const sha1Hash = crypto.createHash('sha1').update(audioData).digest('hex');
867
859
  const identifier = `SND:${sha1Hash}`;
868
860
  // Check if audio with this identifier already exists
869
861
  let audioId;
@@ -894,8 +886,12 @@ class SnapProcessor extends baseProcessor_1.BaseProcessor {
894
886
  * Create a copy of the pageset with audio recordings added
895
887
  */
896
888
  async createAudioEnhancedPageset(sourceDbPath, targetDbPath, audioMappings) {
889
+ if (!(0, io_1.isNodeRuntime)()) {
890
+ throw new Error('createAudioEnhancedPageset is only supported in Node.js environments.');
891
+ }
892
+ const fs = (0, io_1.getFs)();
897
893
  // Copy the source database to target
898
- fs_1.default.copyFileSync(sourceDbPath, targetDbPath);
894
+ fs.copyFileSync(sourceDbPath, targetDbPath);
899
895
  // Add audio recordings to the copy
900
896
  for (const [buttonId, audioInfo] of audioMappings.entries()) {
901
897
  await this.addAudioToButton(targetDbPath, buttonId, audioInfo.audioData, audioInfo.metadata);
@@ -905,7 +901,11 @@ class SnapProcessor extends baseProcessor_1.BaseProcessor {
905
901
  * Extract buttons from a specific page that need audio recordings
906
902
  */
907
903
  extractButtonsForAudio(dbPath, pageUniqueId) {
908
- const db = new better_sqlite3_1.default(dbPath, { readonly: true });
904
+ if (!(0, io_1.isNodeRuntime)()) {
905
+ throw new Error('extractButtonsForAudio is only supported in Node.js environments.');
906
+ }
907
+ const Database = (0, sqlite_1.requireBetterSqlite3)();
908
+ const db = new Database(dbPath, { readonly: true });
909
909
  try {
910
910
  // Find the page by UniqueId
911
911
  const page = db.prepare('SELECT * FROM Page WHERE UniqueId = ?').get(pageUniqueId);
@@ -962,13 +962,19 @@ class SnapProcessor extends baseProcessor_1.BaseProcessor {
962
962
  * @returns Array of available PageLayouts with their dimensions
963
963
  */
964
964
  getAvailablePageLayouts(filePath) {
965
- const dbPath = typeof filePath === 'string' ? filePath : path_1.default.join(process.cwd(), 'temp.spb');
965
+ if (!(0, io_1.isNodeRuntime)()) {
966
+ throw new Error('getAvailablePageLayouts is only supported in Node.js environments.');
967
+ }
968
+ const fs = (0, io_1.getFs)();
969
+ const path = (0, io_1.getPath)();
970
+ const dbPath = typeof filePath === 'string' ? filePath : path.join(process.cwd(), 'temp.spb');
966
971
  if (Buffer.isBuffer(filePath)) {
967
- fs_1.default.writeFileSync(dbPath, filePath);
972
+ fs.writeFileSync(dbPath, filePath);
968
973
  }
969
974
  let db = null;
970
975
  try {
971
- db = new better_sqlite3_1.default(dbPath, { readonly: true });
976
+ const Database = (0, sqlite_1.requireBetterSqlite3)();
977
+ db = new Database(dbPath, { readonly: true });
972
978
  // Get unique PageLayouts based on PageLayoutSetting (dimensions)
973
979
  const pageLayouts = db
974
980
  .prepare(`
@@ -1014,9 +1020,9 @@ class SnapProcessor extends baseProcessor_1.BaseProcessor {
1014
1020
  db.close();
1015
1021
  }
1016
1022
  // Clean up temporary file if created from buffer
1017
- if (Buffer.isBuffer(filePath) && fs_1.default.existsSync(dbPath)) {
1023
+ if (Buffer.isBuffer(filePath) && fs.existsSync(dbPath)) {
1018
1024
  try {
1019
- fs_1.default.unlinkSync(dbPath);
1025
+ fs.unlinkSync(dbPath);
1020
1026
  }
1021
1027
  catch (e) {
1022
1028
  console.warn('Failed to clean up temporary file:', e);
@@ -1,21 +1,15 @@
1
1
  "use strict";
2
- var __importDefault = (this && this.__importDefault) || function (mod) {
3
- return (mod && mod.__esModule) ? mod : { "default": mod };
4
- };
5
2
  Object.defineProperty(exports, "__esModule", { value: true });
6
3
  exports.TouchChatProcessor = void 0;
7
4
  const baseProcessor_1 = require("../core/baseProcessor");
8
5
  const treeStructure_1 = require("../core/treeStructure");
9
6
  const idGenerator_1 = require("../utilities/analytics/utils/idGenerator");
10
7
  const stringCasing_1 = require("../core/stringCasing");
11
- const adm_zip_1 = __importDefault(require("adm-zip"));
12
- const better_sqlite3_1 = __importDefault(require("better-sqlite3"));
13
- const path_1 = __importDefault(require("path"));
14
- const fs_1 = __importDefault(require("fs"));
15
- const os_1 = __importDefault(require("os"));
16
8
  const touchChatValidator_1 = require("../validation/touchChatValidator");
17
9
  const io_1 = require("../utils/io");
18
10
  const translationProcessor_1 = require("../utilities/translation/translationProcessor");
11
+ const sqlite_1 = require("../utils/sqlite");
12
+ const zip_1 = require("../utils/zip");
19
13
  const toNumberOrUndefined = (value) => typeof value === 'number' ? value : undefined;
20
14
  const toStringOrUndefined = (value) => typeof value === 'string' && value.length > 0 ? value : undefined;
21
15
  const toBooleanOrUndefined = (value) => typeof value === 'number' ? value !== 0 : undefined;
@@ -66,25 +60,22 @@ class TouchChatProcessor extends baseProcessor_1.BaseProcessor {
66
60
  async loadIntoTree(filePathOrBuffer) {
67
61
  await Promise.resolve();
68
62
  // Unzip .ce file, extract the .c4v SQLite DB, and parse pages/buttons
69
- let tmpDir = null;
70
63
  let db = null;
64
+ let cleanup;
71
65
  try {
72
66
  // Store source file path or buffer
73
67
  this.sourceFile = filePathOrBuffer;
74
68
  // Step 1: Unzip
75
- tmpDir = fs_1.default.mkdtempSync(path_1.default.join(os_1.default.tmpdir(), 'touchchat-'));
76
69
  const zipInput = (0, io_1.readBinaryFromInput)(filePathOrBuffer);
77
- const zipBuffer = Buffer.isBuffer(zipInput) ? zipInput : Buffer.from(zipInput);
78
- const zip = new adm_zip_1.default(zipBuffer);
79
- zip.extractAllTo(tmpDir, true);
80
- // Step 2: Find and open SQLite DB
81
- const files = fs_1.default.readdirSync(tmpDir);
82
- const vocabFile = files.find((f) => f.endsWith('.c4v'));
83
- if (!vocabFile) {
70
+ const { zip } = await (0, zip_1.openZipFromInput)(zipInput);
71
+ const vocabEntry = zip.listFiles().find((name) => name.endsWith('.c4v'));
72
+ if (!vocabEntry) {
84
73
  throw new Error('No .c4v vocab DB found in TouchChat export');
85
74
  }
86
- const dbPath = path_1.default.join(tmpDir, vocabFile);
87
- db = new better_sqlite3_1.default(dbPath, { readonly: true });
75
+ const dbBuffer = await zip.readFile(vocabEntry);
76
+ const dbResult = await (0, sqlite_1.openSqliteDatabase)(dbBuffer, { readonly: true });
77
+ db = dbResult.db;
78
+ cleanup = dbResult.cleanup;
88
79
  // Step 3: Create tree and load pages
89
80
  const tree = new treeStructure_1.AACTree();
90
81
  // Set root ID to the first page ID (will be updated if we find a better root)
@@ -476,20 +467,18 @@ class TouchChatProcessor extends baseProcessor_1.BaseProcessor {
476
467
  }
477
468
  finally {
478
469
  // Clean up
479
- if (db) {
480
- db.close();
470
+ if (cleanup) {
471
+ cleanup();
481
472
  }
482
- if (tmpDir && fs_1.default.existsSync(tmpDir)) {
483
- try {
484
- fs_1.default.rmSync(tmpDir, { recursive: true, force: true });
485
- }
486
- catch (e) {
487
- console.warn('Failed to clean up temp directory:', e);
488
- }
473
+ else if (db) {
474
+ db.close();
489
475
  }
490
476
  }
491
477
  }
492
478
  async processTexts(filePathOrBuffer, translations, outputPath) {
479
+ if (!(0, io_1.isNodeRuntime)()) {
480
+ throw new Error('processTexts is only supported in Node.js environments for TouchChat files.');
481
+ }
493
482
  // Load the tree, apply translations, and save to new file
494
483
  const tree = await this.loadIntoTree(filePathOrBuffer);
495
484
  // Apply translations to all text content
@@ -519,15 +508,23 @@ class TouchChatProcessor extends baseProcessor_1.BaseProcessor {
519
508
  });
520
509
  // Save the translated tree and return its content
521
510
  await this.saveFromTree(tree, outputPath);
522
- return fs_1.default.readFileSync(outputPath);
511
+ const fs = (0, io_1.getFs)();
512
+ return fs.readFileSync(outputPath);
523
513
  }
524
514
  async saveFromTree(tree, outputPath) {
525
515
  await Promise.resolve();
516
+ if (!(0, io_1.isNodeRuntime)()) {
517
+ throw new Error('saveFromTree is only supported in Node.js environments for TouchChat files.');
518
+ }
519
+ const fs = (0, io_1.getFs)();
520
+ const path = (0, io_1.getPath)();
521
+ const os = (0, io_1.getOs)();
526
522
  // Create a TouchChat database that matches the expected schema for loading
527
- const tmpDir = fs_1.default.mkdtempSync(path_1.default.join(os_1.default.tmpdir(), 'touchchat-export-'));
528
- const dbPath = path_1.default.join(tmpDir, 'vocab.c4v');
523
+ const tmpDir = fs.mkdtempSync(path.join(os.tmpdir(), 'touchchat-export-'));
524
+ const dbPath = path.join(tmpDir, 'vocab.c4v');
529
525
  try {
530
- const db = new better_sqlite3_1.default(dbPath);
526
+ const Database = (0, sqlite_1.requireBetterSqlite3)();
527
+ const db = new Database(dbPath);
531
528
  // Create schema that matches what loadIntoTree expects
532
529
  db.exec(`
533
530
  CREATE TABLE IF NOT EXISTS resources (
@@ -817,14 +814,15 @@ class TouchChatProcessor extends baseProcessor_1.BaseProcessor {
817
814
  }
818
815
  db.close();
819
816
  // Create zip file with the database
820
- const zip = new adm_zip_1.default();
817
+ const AdmZip = (0, io_1.getNodeRequire)()('adm-zip');
818
+ const zip = new AdmZip();
821
819
  zip.addLocalFile(dbPath, '', 'vocab.c4v');
822
820
  zip.writeZip(outputPath);
823
821
  }
824
822
  finally {
825
823
  // Clean up
826
- if (fs_1.default.existsSync(tmpDir)) {
827
- fs_1.default.rmSync(tmpDir, { recursive: true, force: true });
824
+ if (fs.existsSync(tmpDir)) {
825
+ fs.rmSync(tmpDir, { recursive: true, force: true });
828
826
  }
829
827
  }
830
828
  }
@@ -961,6 +959,9 @@ class TouchChatProcessor extends baseProcessor_1.BaseProcessor {
961
959
  * @returns Buffer of the translated TouchChat file
962
960
  */
963
961
  async processLLMTranslations(filePathOrBuffer, llmTranslations, outputPath, options) {
962
+ if (!(0, io_1.isNodeRuntime)()) {
963
+ throw new Error('processLLMTranslations is only supported in Node.js environments for TouchChat files.');
964
+ }
964
965
  const tree = await this.loadIntoTree(filePathOrBuffer);
965
966
  // Validate translations using shared utility
966
967
  const buttonIds = Object.values(tree.pages).flatMap((page) => page.buttons.map((b) => b.id));
@@ -999,7 +1000,8 @@ class TouchChatProcessor extends baseProcessor_1.BaseProcessor {
999
1000
  });
1000
1001
  // Save and return
1001
1002
  await this.saveFromTree(tree, outputPath);
1002
- return fs_1.default.readFileSync(outputPath);
1003
+ const fs = (0, io_1.getFs)();
1004
+ return fs.readFileSync(outputPath);
1003
1005
  }
1004
1006
  }
1005
1007
  exports.TouchChatProcessor = TouchChatProcessor;
@@ -1,7 +1,11 @@
1
1
  export type ProcessorInput = string | Buffer | ArrayBuffer | Uint8Array;
2
2
  export type BinaryOutput = Buffer | Uint8Array;
3
+ type NodeRequire = (id: string) => any;
4
+ export declare function getNodeRequire(): NodeRequire;
3
5
  export declare function getFs(): typeof import('fs');
4
6
  export declare function getPath(): typeof import('path');
7
+ export declare function getOs(): typeof import('os');
8
+ export declare function isNodeRuntime(): boolean;
5
9
  export declare function getBasename(filePath: string): string;
6
10
  export declare function decodeText(input: Uint8Array): string;
7
11
  export declare function encodeBase64(input: Uint8Array): string;
@@ -10,3 +14,4 @@ export declare function readBinaryFromInput(input: ProcessorInput): Uint8Array;
10
14
  export declare function readTextFromInput(input: ProcessorInput, encoding?: BufferEncoding): string;
11
15
  export declare function writeBinaryToPath(outputPath: string, data: BinaryOutput): void;
12
16
  export declare function writeTextToPath(outputPath: string, text: string): void;
17
+ export {};
package/dist/utils/io.js CHANGED
@@ -1,7 +1,10 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.getNodeRequire = getNodeRequire;
3
4
  exports.getFs = getFs;
4
5
  exports.getPath = getPath;
6
+ exports.getOs = getOs;
7
+ exports.isNodeRuntime = isNodeRuntime;
5
8
  exports.getBasename = getBasename;
6
9
  exports.decodeText = decodeText;
7
10
  exports.encodeBase64 = encodeBase64;
@@ -12,6 +15,7 @@ exports.writeBinaryToPath = writeBinaryToPath;
12
15
  exports.writeTextToPath = writeTextToPath;
13
16
  let cachedFs = null;
14
17
  let cachedPath = null;
18
+ let cachedOs = null;
15
19
  let cachedRequire = undefined;
16
20
  function getNodeRequire() {
17
21
  if (cachedRequire === undefined) {
@@ -63,6 +67,25 @@ function getPath() {
63
67
  }
64
68
  return cachedPath;
65
69
  }
70
+ function getOs() {
71
+ if (!cachedOs) {
72
+ try {
73
+ const nodeRequire = getNodeRequire();
74
+ const osModule = 'os';
75
+ cachedOs = nodeRequire(osModule);
76
+ }
77
+ catch {
78
+ throw new Error('OS utilities are not available in this environment.');
79
+ }
80
+ }
81
+ if (!cachedOs) {
82
+ throw new Error('OS utilities are not available in this environment.');
83
+ }
84
+ return cachedOs;
85
+ }
86
+ function isNodeRuntime() {
87
+ return typeof process !== 'undefined' && !!process.versions?.node;
88
+ }
66
89
  function getBasename(filePath) {
67
90
  const parts = filePath.split(/[/\\]/);
68
91
  return parts[parts.length - 1] || filePath;
@@ -0,0 +1,21 @@
1
+ import type { SqlJsConfig } from 'sql.js';
2
+ export interface SqliteStatementAdapter {
3
+ all(...params: unknown[]): any[];
4
+ get(...params: unknown[]): any;
5
+ run(...params: unknown[]): any;
6
+ }
7
+ export interface SqliteDatabaseAdapter {
8
+ prepare(sql: string): SqliteStatementAdapter;
9
+ exec(sql: string): void;
10
+ close(): void;
11
+ }
12
+ export interface SqliteOpenOptions {
13
+ readonly?: boolean;
14
+ }
15
+ export interface SqliteOpenResult {
16
+ db: SqliteDatabaseAdapter;
17
+ cleanup?: () => void;
18
+ }
19
+ export declare function configureSqlJs(config: SqlJsConfig): void;
20
+ export declare function requireBetterSqlite3(): typeof import('better-sqlite3');
21
+ export declare function openSqliteDatabase(input: string | Uint8Array | ArrayBuffer | Buffer, options?: SqliteOpenOptions): Promise<SqliteOpenResult>;