@willwade/aac-processors 0.1.19 → 0.1.21

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (94) hide show
  1. package/dist/browser/core/baseProcessor.js +4 -0
  2. package/dist/browser/processors/applePanelsProcessor.js +24 -31
  3. package/dist/browser/processors/astericsGridProcessor.js +10 -3
  4. package/dist/browser/processors/dotProcessor.js +5 -2
  5. package/dist/browser/processors/gridset/colorUtils.js +354 -0
  6. package/dist/browser/processors/gridset/helpers.js +49 -45
  7. package/dist/browser/processors/gridset/index.js +61 -0
  8. package/dist/browser/processors/gridset/styleHelpers.js +205 -0
  9. package/dist/browser/processors/gridset/symbolExtractor.js +331 -0
  10. package/dist/browser/processors/gridset/symbolSearch.js +248 -0
  11. package/dist/browser/processors/gridset/symbols.js +35 -68
  12. package/dist/browser/processors/gridsetProcessor.js +32 -41
  13. package/dist/browser/processors/obfProcessor.js +53 -45
  14. package/dist/browser/processors/opmlProcessor.js +5 -2
  15. package/dist/browser/processors/snap/helpers.js +49 -45
  16. package/dist/browser/processors/snapProcessor.js +67 -31
  17. package/dist/browser/processors/touchchatProcessor.js +54 -45
  18. package/dist/browser/utilities/analytics/reference/index.js +27 -19
  19. package/dist/browser/utils/io.js +67 -14
  20. package/dist/browser/utils/sqlite.js +6 -8
  21. package/dist/browser/utils/zip.js +45 -43
  22. package/dist/browser/validation/baseValidator.js +5 -0
  23. package/dist/browser/validation/gridsetValidator.js +12 -20
  24. package/dist/browser/validation/obfValidator.js +5 -4
  25. package/dist/browser/validation/snapValidator.js +9 -5
  26. package/dist/browser/validation/touchChatValidator.js +21 -11
  27. package/dist/cli/index.js +10 -15
  28. package/dist/core/baseProcessor.d.ts +7 -7
  29. package/dist/core/baseProcessor.js +4 -0
  30. package/dist/processors/applePanelsProcessor.js +29 -36
  31. package/dist/processors/astericsGridProcessor.js +20 -13
  32. package/dist/processors/dotProcessor.js +10 -7
  33. package/dist/processors/excelProcessor.js +9 -12
  34. package/dist/processors/gridset/helpers.d.ts +9 -11
  35. package/dist/processors/gridset/helpers.js +49 -71
  36. package/dist/processors/gridset/imageDebug.d.ts +3 -5
  37. package/dist/processors/gridset/imageDebug.js +4 -4
  38. package/dist/processors/gridset/password.d.ts +1 -1
  39. package/dist/processors/gridset/symbolExtractor.d.ts +5 -3
  40. package/dist/processors/gridset/symbolExtractor.js +15 -38
  41. package/dist/processors/gridset/symbolSearch.d.ts +3 -2
  42. package/dist/processors/gridset/symbolSearch.js +12 -34
  43. package/dist/processors/gridset/symbols.d.ts +8 -6
  44. package/dist/processors/gridset/symbols.js +34 -67
  45. package/dist/processors/gridset/wordlistHelpers.d.ts +4 -6
  46. package/dist/processors/gridset/wordlistHelpers.js +15 -74
  47. package/dist/processors/gridsetProcessor.js +36 -68
  48. package/dist/processors/obfProcessor.js +58 -73
  49. package/dist/processors/obfsetProcessor.js +2 -2
  50. package/dist/processors/opmlProcessor.js +10 -7
  51. package/dist/processors/snap/helpers.d.ts +8 -8
  52. package/dist/processors/snap/helpers.js +50 -72
  53. package/dist/processors/snapProcessor.js +66 -30
  54. package/dist/processors/touchchatProcessor.js +54 -45
  55. package/dist/utilities/analytics/index.d.ts +3 -2
  56. package/dist/utilities/analytics/index.js +8 -10
  57. package/dist/utilities/analytics/reference/index.d.ts +5 -3
  58. package/dist/utilities/analytics/reference/index.js +26 -18
  59. package/dist/utilities/symbolTools.d.ts +4 -2
  60. package/dist/utilities/symbolTools.js +16 -15
  61. package/dist/utils/io.d.ts +24 -6
  62. package/dist/utils/io.js +64 -14
  63. package/dist/utils/sqlite.d.ts +2 -0
  64. package/dist/utils/sqlite.js +6 -8
  65. package/dist/utils/zip.d.ts +7 -3
  66. package/dist/utils/zip.js +45 -43
  67. package/dist/validation/applePanelsValidator.d.ts +2 -1
  68. package/dist/validation/applePanelsValidator.js +9 -11
  69. package/dist/validation/astericsValidator.d.ts +2 -1
  70. package/dist/validation/astericsValidator.js +5 -4
  71. package/dist/validation/baseValidator.d.ts +2 -2
  72. package/dist/validation/baseValidator.js +5 -0
  73. package/dist/validation/dotValidator.d.ts +2 -1
  74. package/dist/validation/dotValidator.js +5 -4
  75. package/dist/validation/excelValidator.d.ts +2 -1
  76. package/dist/validation/excelValidator.js +5 -4
  77. package/dist/validation/gridsetValidator.d.ts +2 -1
  78. package/dist/validation/gridsetValidator.js +11 -22
  79. package/dist/validation/index.d.ts +2 -2
  80. package/dist/validation/index.js +5 -4
  81. package/dist/validation/obfValidator.d.ts +2 -1
  82. package/dist/validation/obfValidator.js +5 -4
  83. package/dist/validation/obfsetValidator.d.ts +2 -1
  84. package/dist/validation/obfsetValidator.js +5 -4
  85. package/dist/validation/opmlValidator.d.ts +2 -1
  86. package/dist/validation/opmlValidator.js +5 -4
  87. package/dist/validation/snapValidator.d.ts +2 -1
  88. package/dist/validation/snapValidator.js +9 -5
  89. package/dist/validation/touchChatValidator.d.ts +4 -6
  90. package/dist/validation/touchChatValidator.js +21 -11
  91. package/dist/validation/validationTypes.d.ts +8 -1
  92. package/package.json +1 -1
  93. package/dist/core/fileProcessor.d.ts +0 -7
  94. package/dist/core/fileProcessor.js +0 -52
@@ -38,15 +38,15 @@ function mapSnapVisibility(visible) {
38
38
  return visible === 0 ? 'Hidden' : 'Visible';
39
39
  }
40
40
  class SnapProcessor extends baseProcessor_1.BaseProcessor {
41
- constructor(symbolResolver = null, options = {}) {
41
+ constructor(symbolResolver = null, options) {
42
42
  super(options);
43
43
  this.symbolResolver = null;
44
44
  this.loadAudio = false;
45
45
  this.pageLayoutPreference = 'scanning'; // Default to scanning for metrics
46
46
  this.symbolResolver = symbolResolver;
47
- this.loadAudio = options.loadAudio !== undefined ? options.loadAudio : true;
47
+ this.loadAudio = options?.loadAudio !== undefined ? options.loadAudio : true;
48
48
  this.pageLayoutPreference =
49
- options.pageLayoutPreference !== undefined ? options.pageLayoutPreference : 'scanning'; // Default to scanning
49
+ options?.pageLayoutPreference !== undefined ? options.pageLayoutPreference : 'scanning'; // Default to scanning
50
50
  }
51
51
  async extractTexts(filePathOrBuffer) {
52
52
  const tree = await this.loadIntoTree(filePathOrBuffer);
@@ -67,11 +67,41 @@ class SnapProcessor extends baseProcessor_1.BaseProcessor {
67
67
  return texts;
68
68
  }
69
69
  async loadIntoTree(filePathOrBuffer) {
70
+ const { writeBinaryToPath, removePath, mkTempDir, basename, join } = this.options.fileAdapter;
70
71
  await Promise.resolve();
71
72
  const tree = new treeStructure_1.AACTree();
72
73
  let dbResult = null;
74
+ let cleanupTempZip = null;
73
75
  try {
74
- dbResult = await (0, sqlite_1.openSqliteDatabase)(filePathOrBuffer, { readonly: true });
76
+ // Handle .sub.zip files (Snap pageset backups containing .sps files)
77
+ let inputFile = filePathOrBuffer;
78
+ if (typeof filePathOrBuffer === 'string') {
79
+ const fileName = basename(filePathOrBuffer).toLowerCase();
80
+ if (fileName.endsWith('.sub.zip') || filePathOrBuffer.endsWith('.sub')) {
81
+ // Extract .sub.zip to find the embedded .sps file
82
+ const tempDir = mkTempDir('snap-sub-');
83
+ const zip = await this.options.zipAdapter(filePathOrBuffer);
84
+ // Find the .sps file in the archive
85
+ const files = zip.listFiles();
86
+ const spsFile = files.find((f) => f.endsWith('.sps'));
87
+ if (!spsFile) {
88
+ removePath(tempDir, { recursive: true, force: true });
89
+ throw new Error('No .sps file found in .sub.zip archive');
90
+ }
91
+ // Extract the .sps file
92
+ const spsData = await zip.readFile(spsFile);
93
+ const extractedSpsPath = join(tempDir, basename(spsFile));
94
+ writeBinaryToPath(extractedSpsPath, Buffer.from(spsData));
95
+ inputFile = extractedSpsPath;
96
+ cleanupTempZip = () => {
97
+ removePath(tempDir, { recursive: true, force: true });
98
+ };
99
+ }
100
+ }
101
+ dbResult = await (0, sqlite_1.openSqliteDatabase)(inputFile, {
102
+ readonly: true,
103
+ fileAdapter: this.options.fileAdapter,
104
+ });
75
105
  const db = dbResult.db;
76
106
  const getTableColumns = (tableName) => {
77
107
  try {
@@ -661,24 +691,32 @@ class SnapProcessor extends baseProcessor_1.BaseProcessor {
661
691
  else if (dbResult?.db) {
662
692
  dbResult.db.close();
663
693
  }
694
+ // Clean up temporary extracted .sps file from .sub.zip
695
+ if (cleanupTempZip) {
696
+ try {
697
+ cleanupTempZip();
698
+ }
699
+ catch (e) {
700
+ console.warn('[SnapProcessor] Failed to clean up temporary .sps file:', e);
701
+ }
702
+ }
664
703
  }
665
704
  }
666
705
  async processTexts(filePathOrBuffer, translations, outputPath) {
706
+ const { pathExists, mkDir, writeBinaryToPath, readBinaryFromInput, removePath, dirname } = this.options.fileAdapter;
667
707
  if (!(0, io_1.isNodeRuntime)()) {
668
708
  throw new Error('processTexts is only supported in Node.js environments for Snap files.');
669
709
  }
670
- const fs = (0, io_1.getFs)();
671
- const path = (0, io_1.getPath)();
672
710
  if (typeof filePathOrBuffer === 'string') {
673
711
  const inputPath = filePathOrBuffer;
674
- const outputDir = path.dirname(outputPath);
675
- if (!fs.existsSync(outputDir)) {
676
- fs.mkdirSync(outputDir, { recursive: true });
712
+ const outputDir = dirname(outputPath);
713
+ if (!pathExists(outputDir)) {
714
+ mkDir(outputDir, { recursive: true });
677
715
  }
678
- if (fs.existsSync(outputPath)) {
679
- fs.unlinkSync(outputPath);
716
+ if (pathExists(outputPath)) {
717
+ removePath(outputPath);
680
718
  }
681
- fs.copyFileSync(inputPath, outputPath);
719
+ writeBinaryToPath(outputPath, readBinaryFromInput(inputPath));
682
720
  const Database = (0, sqlite_1.requireBetterSqlite3)();
683
721
  const db = new Database(outputPath, { readonly: false });
684
722
  try {
@@ -740,7 +778,7 @@ class SnapProcessor extends baseProcessor_1.BaseProcessor {
740
778
  finally {
741
779
  db.close();
742
780
  }
743
- return fs.readFileSync(outputPath);
781
+ return readBinaryFromInput(outputPath);
744
782
  }
745
783
  // Fallback for buffer inputs: rebuild from tree (may drop Snap assets)
746
784
  const tree = await this.loadIntoTree(filePathOrBuffer);
@@ -767,21 +805,20 @@ class SnapProcessor extends baseProcessor_1.BaseProcessor {
767
805
  });
768
806
  });
769
807
  await this.saveFromTree(tree, outputPath);
770
- return fs.readFileSync(outputPath);
808
+ return readBinaryFromInput(outputPath);
771
809
  }
772
810
  async saveFromTree(tree, outputPath) {
811
+ const { pathExists, mkDir, removePath, dirname } = this.options.fileAdapter;
773
812
  if (!(0, io_1.isNodeRuntime)()) {
774
813
  throw new Error('saveFromTree is only supported in Node.js environments for Snap files.');
775
814
  }
776
815
  await Promise.resolve();
777
- const fs = (0, io_1.getFs)();
778
- const path = (0, io_1.getPath)();
779
- const outputDir = path.dirname(outputPath);
780
- if (!fs.existsSync(outputDir)) {
781
- fs.mkdirSync(outputDir, { recursive: true });
816
+ const outputDir = dirname(outputPath);
817
+ if (!pathExists(outputDir)) {
818
+ mkDir(outputDir, { recursive: true });
782
819
  }
783
- if (fs.existsSync(outputPath)) {
784
- fs.unlinkSync(outputPath);
820
+ if (pathExists(outputPath)) {
821
+ removePath(outputPath);
785
822
  }
786
823
  // Create a new SQLite database for Snap format
787
824
  const Database = (0, sqlite_1.requireBetterSqlite3)();
@@ -1064,12 +1101,12 @@ class SnapProcessor extends baseProcessor_1.BaseProcessor {
1064
1101
  * Create a copy of the pageset with audio recordings added
1065
1102
  */
1066
1103
  async createAudioEnhancedPageset(sourceDbPath, targetDbPath, audioMappings) {
1104
+ const { writeBinaryToPath, readBinaryFromInput } = this.options.fileAdapter;
1067
1105
  if (!(0, io_1.isNodeRuntime)()) {
1068
1106
  throw new Error('createAudioEnhancedPageset is only supported in Node.js environments.');
1069
1107
  }
1070
- const fs = (0, io_1.getFs)();
1071
1108
  // Copy the source database to target
1072
- fs.copyFileSync(sourceDbPath, targetDbPath);
1109
+ writeBinaryToPath(targetDbPath, readBinaryFromInput(sourceDbPath));
1073
1110
  // Add audio recordings to the copy
1074
1111
  for (const [buttonId, audioInfo] of audioMappings.entries()) {
1075
1112
  await this.addAudioToButton(targetDbPath, buttonId, audioInfo.audioData, audioInfo.metadata);
@@ -1131,7 +1168,7 @@ class SnapProcessor extends baseProcessor_1.BaseProcessor {
1131
1168
  * @returns Promise with validation result
1132
1169
  */
1133
1170
  async validate(filePath) {
1134
- return snapValidator_1.SnapValidator.validateFile(filePath);
1171
+ return snapValidator_1.SnapValidator.validateFile(filePath, this.options.fileAdapter);
1135
1172
  }
1136
1173
  /**
1137
1174
  * Get available PageLayouts for a Snap file
@@ -1140,14 +1177,13 @@ class SnapProcessor extends baseProcessor_1.BaseProcessor {
1140
1177
  * @returns Array of available PageLayouts with their dimensions
1141
1178
  */
1142
1179
  getAvailablePageLayouts(filePath) {
1180
+ const { writeBinaryToPath, removePath, pathExists, join } = this.options.fileAdapter;
1143
1181
  if (!(0, io_1.isNodeRuntime)()) {
1144
1182
  throw new Error('getAvailablePageLayouts is only supported in Node.js environments.');
1145
1183
  }
1146
- const fs = (0, io_1.getFs)();
1147
- const path = (0, io_1.getPath)();
1148
- const dbPath = typeof filePath === 'string' ? filePath : path.join(process.cwd(), 'temp.spb');
1184
+ const dbPath = typeof filePath === 'string' ? filePath : join(process.cwd(), 'temp.spb');
1149
1185
  if (Buffer.isBuffer(filePath)) {
1150
- fs.writeFileSync(dbPath, filePath);
1186
+ writeBinaryToPath(dbPath, filePath);
1151
1187
  }
1152
1188
  let db = null;
1153
1189
  try {
@@ -1198,9 +1234,9 @@ class SnapProcessor extends baseProcessor_1.BaseProcessor {
1198
1234
  db.close();
1199
1235
  }
1200
1236
  // Clean up temporary file if created from buffer
1201
- if (Buffer.isBuffer(filePath) && fs.existsSync(dbPath)) {
1237
+ if (Buffer.isBuffer(filePath) && pathExists(dbPath)) {
1202
1238
  try {
1203
- fs.unlinkSync(dbPath);
1239
+ removePath(dbPath);
1204
1240
  }
1205
1241
  catch (e) {
1206
1242
  console.warn('Failed to clean up temporary file:', e);
@@ -9,7 +9,7 @@ const touchChatValidator_1 = require("../validation/touchChatValidator");
9
9
  const io_1 = require("../utils/io");
10
10
  const translationProcessor_1 = require("../utilities/translation/translationProcessor");
11
11
  const sqlite_1 = require("../utils/sqlite");
12
- const zip_1 = require("../utils/zip");
12
+ const gridset_1 = require("./gridset");
13
13
  const toNumberOrUndefined = (value) => typeof value === 'number' ? value : undefined;
14
14
  const toStringOrUndefined = (value) => typeof value === 'string' && value.length > 0 ? value : undefined;
15
15
  const toBooleanOrUndefined = (value) => typeof value === 'number' ? value !== 0 : undefined;
@@ -58,6 +58,7 @@ class TouchChatProcessor extends baseProcessor_1.BaseProcessor {
58
58
  return texts;
59
59
  }
60
60
  async loadIntoTree(filePathOrBuffer) {
61
+ const { readBinaryFromInput } = this.options.fileAdapter;
61
62
  await Promise.resolve();
62
63
  // Unzip .ce file, extract the .c4v SQLite DB, and parse pages/buttons
63
64
  let db = null;
@@ -66,16 +67,17 @@ class TouchChatProcessor extends baseProcessor_1.BaseProcessor {
66
67
  // Store source file path or buffer
67
68
  this.sourceFile = filePathOrBuffer;
68
69
  // Step 1: Unzip
69
- const zipInput = (0, io_1.readBinaryFromInput)(filePathOrBuffer);
70
- const { zip } = this.options.zipAdapter
71
- ? await this.options.zipAdapter(zipInput)
72
- : await (0, zip_1.openZipFromInput)(zipInput);
70
+ const zipInput = readBinaryFromInput(filePathOrBuffer);
71
+ const zip = await this.options.zipAdapter(zipInput);
73
72
  const vocabEntry = zip.listFiles().find((name) => name.endsWith('.c4v'));
74
73
  if (!vocabEntry) {
75
74
  throw new Error('No .c4v vocab DB found in TouchChat export');
76
75
  }
77
76
  const dbBuffer = await zip.readFile(vocabEntry);
78
- const dbResult = await (0, sqlite_1.openSqliteDatabase)(dbBuffer, { readonly: true });
77
+ const dbResult = await (0, sqlite_1.openSqliteDatabase)(dbBuffer, {
78
+ readonly: true,
79
+ fileAdapter: this.options.fileAdapter,
80
+ });
79
81
  db = dbResult.db;
80
82
  cleanup = dbResult.cleanup;
81
83
  // Step 3: Create tree and load pages
@@ -478,6 +480,7 @@ class TouchChatProcessor extends baseProcessor_1.BaseProcessor {
478
480
  }
479
481
  }
480
482
  async processTexts(filePathOrBuffer, translations, outputPath) {
483
+ const { pathExists, mkDir, removePath, mkTempDir, writeBinaryToPath, readBinaryFromInput, dirname, join, } = this.options.fileAdapter;
481
484
  if (!(0, io_1.isNodeRuntime)()) {
482
485
  throw new Error('processTexts is only supported in Node.js environments for TouchChat files.');
483
486
  }
@@ -490,28 +493,24 @@ class TouchChatProcessor extends baseProcessor_1.BaseProcessor {
490
493
  * within the embedded SQLite database, ensuring assets and metadata remain intact.
491
494
  */
492
495
  if (typeof filePathOrBuffer === 'string') {
493
- const fs = (0, io_1.getFs)();
494
- const path = (0, io_1.getPath)();
495
- const os = (0, io_1.getOs)();
496
- const AdmZip = (0, io_1.getNodeRequire)()('adm-zip');
497
496
  const inputPath = filePathOrBuffer;
498
- const outputDir = path.dirname(outputPath);
499
- if (!fs.existsSync(outputDir)) {
500
- fs.mkdirSync(outputDir, { recursive: true });
497
+ const outputDir = dirname(outputPath);
498
+ if (!pathExists(outputDir)) {
499
+ mkDir(outputDir, { recursive: true });
501
500
  }
502
- if (fs.existsSync(outputPath)) {
503
- fs.unlinkSync(outputPath);
501
+ if (pathExists(outputPath)) {
502
+ removePath(outputPath);
504
503
  }
505
- const zip = new AdmZip(inputPath);
506
- const entries = zip.getEntries();
504
+ const zip = await this.options.zipAdapter(inputPath);
505
+ const entries = (0, gridset_1.getZipEntriesFromAdapter)(zip);
507
506
  const vocabEntry = entries.find((entry) => entry.entryName.endsWith('.c4v'));
508
507
  if (!vocabEntry) {
509
508
  throw new Error('No .c4v vocab DB found in TouchChat export');
510
509
  }
511
- const tempDir = fs.mkdtempSync(path.join(os.tmpdir(), 'touchchat-translate-'));
512
- const dbPath = path.join(tempDir, 'vocab.c4v');
510
+ const tempDir = mkTempDir('touchchat-translate-');
511
+ const dbPath = join(tempDir, 'vocab.c4v');
513
512
  try {
514
- fs.writeFileSync(dbPath, vocabEntry.getData());
513
+ writeBinaryToPath(dbPath, await vocabEntry.getData());
515
514
  const Database = (0, sqlite_1.requireBetterSqlite3)();
516
515
  const db = new Database(dbPath, { readonly: false });
517
516
  try {
@@ -564,26 +563,34 @@ class TouchChatProcessor extends baseProcessor_1.BaseProcessor {
564
563
  finally {
565
564
  db.close();
566
565
  }
567
- const outputZip = new AdmZip();
568
- entries.forEach((entry) => {
566
+ const outputZip = await this.options.zipAdapter();
567
+ const files = [];
568
+ for (const entry of entries) {
569
569
  if (entry.entryName === vocabEntry.entryName) {
570
- return;
570
+ continue;
571
571
  }
572
- const data = entry.isDirectory ? Buffer.alloc(0) : entry.getData();
573
- outputZip.addFile(entry.entryName, data, entry.comment || '');
572
+ const data = await entry.getData();
573
+ files.push({
574
+ name: entry.entryName,
575
+ data,
576
+ });
577
+ }
578
+ files.push({
579
+ name: vocabEntry.entryName,
580
+ data: readBinaryFromInput(dbPath),
574
581
  });
575
- outputZip.addFile(vocabEntry.entryName, fs.readFileSync(dbPath));
576
- outputZip.writeZip(outputPath);
582
+ const zipData = await outputZip.writeFiles(files);
583
+ writeBinaryToPath(outputPath, zipData);
577
584
  }
578
585
  finally {
579
586
  try {
580
- fs.rmSync(tempDir, { recursive: true, force: true });
587
+ removePath(tempDir, { recursive: true, force: true });
581
588
  }
582
589
  catch {
583
590
  // Best-effort cleanup
584
591
  }
585
592
  }
586
- return fs.readFileSync(outputPath);
593
+ return readBinaryFromInput(outputPath);
587
594
  }
588
595
  // Fallback for buffer inputs: rebuild from tree (may drop TouchChat metadata)
589
596
  const tree = await this.loadIntoTree(filePathOrBuffer);
@@ -610,20 +617,17 @@ class TouchChatProcessor extends baseProcessor_1.BaseProcessor {
610
617
  });
611
618
  });
612
619
  await this.saveFromTree(tree, outputPath);
613
- const fs = (0, io_1.getFs)();
614
- return fs.readFileSync(outputPath);
620
+ return readBinaryFromInput(outputPath);
615
621
  }
616
622
  async saveFromTree(tree, outputPath) {
623
+ const { writeBinaryToPath, mkTempDir, readBinaryFromInput, pathExists, removePath, join } = this.options.fileAdapter;
617
624
  await Promise.resolve();
618
625
  if (!(0, io_1.isNodeRuntime)()) {
619
626
  throw new Error('saveFromTree is only supported in Node.js environments for TouchChat files.');
620
627
  }
621
- const fs = (0, io_1.getFs)();
622
- const path = (0, io_1.getPath)();
623
- const os = (0, io_1.getOs)();
624
628
  // Create a TouchChat database that matches the expected schema for loading
625
- const tmpDir = fs.mkdtempSync(path.join(os.tmpdir(), 'touchchat-export-'));
626
- const dbPath = path.join(tmpDir, 'vocab.c4v');
629
+ const tmpDir = mkTempDir('touchchat-export-');
630
+ const dbPath = join(tmpDir, 'vocab.c4v');
627
631
  try {
628
632
  const Database = (0, sqlite_1.requireBetterSqlite3)();
629
633
  const db = new Database(dbPath);
@@ -916,15 +920,20 @@ class TouchChatProcessor extends baseProcessor_1.BaseProcessor {
916
920
  }
917
921
  db.close();
918
922
  // Create zip file with the database
919
- const AdmZip = (0, io_1.getNodeRequire)()('adm-zip');
920
- const zip = new AdmZip();
921
- zip.addLocalFile(dbPath, '', 'vocab.c4v');
922
- zip.writeZip(outputPath);
923
+ const zip = await this.options.zipAdapter();
924
+ const data = readBinaryFromInput(dbPath);
925
+ const zipData = await zip.writeFiles([
926
+ {
927
+ name: 'vocab.c4v',
928
+ data,
929
+ },
930
+ ]);
931
+ writeBinaryToPath(outputPath, zipData);
923
932
  }
924
933
  finally {
925
934
  // Clean up
926
- if (fs.existsSync(tmpDir)) {
927
- fs.rmSync(tmpDir, { recursive: true, force: true });
935
+ if (pathExists(tmpDir)) {
936
+ removePath(tmpDir, { recursive: true, force: true });
928
937
  }
929
938
  }
930
939
  }
@@ -1019,7 +1028,7 @@ class TouchChatProcessor extends baseProcessor_1.BaseProcessor {
1019
1028
  * @returns Promise with validation result
1020
1029
  */
1021
1030
  async validate(filePath) {
1022
- return touchChatValidator_1.TouchChatValidator.validateFile(filePath);
1031
+ return touchChatValidator_1.TouchChatValidator.validateFile(filePath, this.options.fileAdapter);
1023
1032
  }
1024
1033
  /**
1025
1034
  * Extract symbol information from a TouchChat file for LLM-based translation.
@@ -1061,6 +1070,7 @@ class TouchChatProcessor extends baseProcessor_1.BaseProcessor {
1061
1070
  * @returns Buffer of the translated TouchChat file
1062
1071
  */
1063
1072
  async processLLMTranslations(filePathOrBuffer, llmTranslations, outputPath, options) {
1073
+ const { readBinaryFromInput } = this.options.fileAdapter;
1064
1074
  if (!(0, io_1.isNodeRuntime)()) {
1065
1075
  throw new Error('processLLMTranslations is only supported in Node.js environments for TouchChat files.');
1066
1076
  }
@@ -1102,8 +1112,7 @@ class TouchChatProcessor extends baseProcessor_1.BaseProcessor {
1102
1112
  });
1103
1113
  // Save and return
1104
1114
  await this.saveFromTree(tree, outputPath);
1105
- const fs = (0, io_1.getFs)();
1106
- return fs.readFileSync(outputPath);
1115
+ return readBinaryFromInput(outputPath);
1107
1116
  }
1108
1117
  }
1109
1118
  exports.TouchChatProcessor = TouchChatProcessor;
@@ -9,6 +9,7 @@
9
9
  *
10
10
  * @module
11
11
  */
12
+ import { FileAdapter } from '../../utils/io';
12
13
  export * from './metrics/types';
13
14
  export * from './metrics/effort';
14
15
  export * from './utils/idGenerator';
@@ -23,8 +24,8 @@ export { ReferenceLoader } from './reference';
23
24
  /**
24
25
  * Get the default reference data path
25
26
  */
26
- export declare function getReferenceDataPath(): string;
27
+ export declare function getReferenceDataPath(fileAdapter: FileAdapter): string;
27
28
  /**
28
29
  * Check if reference data files exist
29
30
  */
30
- export declare function hasReferenceData(): boolean;
31
+ export declare function hasReferenceData(fileAdapter?: FileAdapter): boolean;
@@ -24,15 +24,11 @@ var __createBinding = (this && this.__createBinding) || (Object.create ? (functi
24
24
  var __exportStar = (this && this.__exportStar) || function(m, exports) {
25
25
  for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
26
26
  };
27
- var __importDefault = (this && this.__importDefault) || function (mod) {
28
- return (mod && mod.__esModule) ? mod : { "default": mod };
29
- };
30
27
  Object.defineProperty(exports, "__esModule", { value: true });
31
28
  exports.ReferenceLoader = exports.ComparisonAnalyzer = exports.SentenceAnalyzer = exports.VocabularyAnalyzer = exports.MetricsCalculator = exports.OblAnonymizer = exports.OblUtil = void 0;
32
29
  exports.getReferenceDataPath = getReferenceDataPath;
33
30
  exports.hasReferenceData = hasReferenceData;
34
- const path_1 = __importDefault(require("path"));
35
- const fs_1 = __importDefault(require("fs"));
31
+ const io_1 = require("../../utils/io");
36
32
  // Always-available exports
37
33
  __exportStar(require("./metrics/types"), exports);
38
34
  __exportStar(require("./metrics/effort"), exports);
@@ -59,14 +55,16 @@ Object.defineProperty(exports, "ReferenceLoader", { enumerable: true, get: funct
59
55
  /**
60
56
  * Get the default reference data path
61
57
  */
62
- function getReferenceDataPath() {
63
- return path_1.default.join(__dirname, 'reference', 'data');
58
+ function getReferenceDataPath(fileAdapter) {
59
+ const { join } = fileAdapter;
60
+ return join(__dirname, 'reference', 'data');
64
61
  }
65
62
  /**
66
63
  * Check if reference data files exist
67
64
  */
68
- function hasReferenceData() {
69
- const dataPath = getReferenceDataPath();
65
+ function hasReferenceData(fileAdapter = io_1.defaultFileAdapter) {
66
+ const { pathExists, join } = fileAdapter;
67
+ const dataPath = getReferenceDataPath(fileAdapter);
70
68
  const requiredFiles = [
71
69
  'core_lists.en.json',
72
70
  'common_words.en.json',
@@ -74,5 +72,5 @@ function hasReferenceData() {
74
72
  'synonyms.en.json',
75
73
  'fringe.en.json',
76
74
  ];
77
- return requiredFiles.every((file) => fs_1.default.existsSync(path_1.default.join(dataPath, file)));
75
+ return requiredFiles.every((file) => pathExists(join(dataPath, file)));
78
76
  }
@@ -5,6 +5,7 @@
5
5
  * for AAC metrics analysis.
6
6
  */
7
7
  import { CoreList, CommonWordsData, SynonymsData } from '../metrics/types';
8
+ import { FileAdapter } from '../../../utils/io';
8
9
  export interface ReferenceDataProvider {
9
10
  loadCoreLists(): CoreList[];
10
11
  loadCommonWords(): CommonWordsData;
@@ -29,7 +30,8 @@ export interface ReferenceDataProvider {
29
30
  export declare class ReferenceLoader {
30
31
  private dataDir;
31
32
  private locale;
32
- constructor(dataDir?: string, locale?: string);
33
+ private fileAdapter;
34
+ constructor(dataDir?: string, locale?: string, fileAdapter?: FileAdapter);
33
35
  /**
34
36
  * Load core vocabulary lists
35
37
  */
@@ -79,8 +81,8 @@ export declare class ReferenceLoader {
79
81
  /**
80
82
  * Get the default reference data path
81
83
  */
82
- export declare function getReferenceDataPath(): string;
84
+ export declare function getReferenceDataPath(fileAdapter?: FileAdapter): string;
83
85
  /**
84
86
  * Check if reference data files exist
85
87
  */
86
- export declare function hasReferenceData(): boolean;
88
+ export declare function hasReferenceData(fileAdapter?: FileAdapter): boolean;
@@ -11,55 +11,61 @@ exports.getReferenceDataPath = getReferenceDataPath;
11
11
  exports.hasReferenceData = hasReferenceData;
12
12
  const io_1 = require("../../../utils/io");
13
13
  class ReferenceLoader {
14
- constructor(dataDir, locale = 'en') {
14
+ constructor(dataDir, locale = 'en', fileAdapter = io_1.defaultFileAdapter) {
15
15
  this.locale = locale;
16
+ this.fileAdapter = fileAdapter;
16
17
  if (dataDir) {
17
18
  this.dataDir = dataDir;
18
19
  }
19
20
  else {
20
21
  // Resolve the data directory relative to this file's location
21
22
  // Use __dirname which works correctly after compilation
22
- this.dataDir = (0, io_1.getPath)().join(__dirname, 'data');
23
+ this.dataDir = this.fileAdapter.join(__dirname, 'data');
23
24
  }
24
25
  }
25
26
  /**
26
27
  * Load core vocabulary lists
27
28
  */
28
29
  loadCoreLists() {
29
- const filePath = (0, io_1.getPath)().join(this.dataDir, `core_lists.${this.locale}.json`);
30
- const content = (0, io_1.getFs)().readFileSync(filePath, 'utf-8');
30
+ const { readTextFromInput } = this.fileAdapter;
31
+ const filePath = this.fileAdapter.join(this.dataDir, `core_lists.${this.locale}.json`);
32
+ const content = readTextFromInput(filePath);
31
33
  return JSON.parse(String(content));
32
34
  }
33
35
  /**
34
36
  * Load common words with baseline effort scores
35
37
  */
36
38
  loadCommonWords() {
37
- const filePath = (0, io_1.getPath)().join(this.dataDir, `common_words.${this.locale}.json`);
38
- const content = (0, io_1.getFs)().readFileSync(filePath, 'utf-8');
39
+ const { readTextFromInput, join } = this.fileAdapter;
40
+ const filePath = join(this.dataDir, `common_words.${this.locale}.json`);
41
+ const content = readTextFromInput(filePath);
39
42
  return JSON.parse(String(content));
40
43
  }
41
44
  /**
42
45
  * Load synonym mappings
43
46
  */
44
47
  loadSynonyms() {
45
- const filePath = (0, io_1.getPath)().join(this.dataDir, `synonyms.${this.locale}.json`);
46
- const content = (0, io_1.getFs)().readFileSync(filePath, 'utf-8');
48
+ const { readTextFromInput, join } = this.fileAdapter;
49
+ const filePath = join(this.dataDir, `synonyms.${this.locale}.json`);
50
+ const content = readTextFromInput(filePath);
47
51
  return JSON.parse(String(content));
48
52
  }
49
53
  /**
50
54
  * Load test sentences
51
55
  */
52
56
  loadSentences() {
53
- const filePath = (0, io_1.getPath)().join(this.dataDir, `sentences.${this.locale}.json`);
54
- const content = (0, io_1.getFs)().readFileSync(filePath, 'utf-8');
57
+ const { readTextFromInput, join } = this.fileAdapter;
58
+ const filePath = join(this.dataDir, `sentences.${this.locale}.json`);
59
+ const content = readTextFromInput(filePath);
55
60
  return JSON.parse(String(content));
56
61
  }
57
62
  /**
58
63
  * Load fringe vocabulary
59
64
  */
60
65
  loadFringe() {
61
- const filePath = (0, io_1.getPath)().join(this.dataDir, `fringe.${this.locale}.json`);
62
- const content = (0, io_1.getFs)().readFileSync(filePath, 'utf-8');
66
+ const { readTextFromInput, join } = this.fileAdapter;
67
+ const filePath = join(this.dataDir, `fringe.${this.locale}.json`);
68
+ const content = readTextFromInput(filePath);
63
69
  const data = JSON.parse(String(content));
64
70
  // Flatten nested category words if needed
65
71
  if (Array.isArray(data) && data.length > 0 && data[0].categories) {
@@ -77,8 +83,9 @@ class ReferenceLoader {
77
83
  * Load base words hash map
78
84
  */
79
85
  loadBaseWords() {
80
- const filePath = (0, io_1.getPath)().join(this.dataDir, `base_words.${this.locale}.json`);
81
- const content = (0, io_1.getFs)().readFileSync(filePath, 'utf-8');
86
+ const { readTextFromInput, join } = this.fileAdapter;
87
+ const filePath = join(this.dataDir, `base_words.${this.locale}.json`);
88
+ const content = readTextFromInput(filePath);
82
89
  return JSON.parse(String(content));
83
90
  }
84
91
  /**
@@ -116,13 +123,14 @@ exports.ReferenceLoader = ReferenceLoader;
116
123
  /**
117
124
  * Get the default reference data path
118
125
  */
119
- function getReferenceDataPath() {
120
- return String((0, io_1.getPath)().join(__dirname, 'data'));
126
+ function getReferenceDataPath(fileAdapter = io_1.defaultFileAdapter) {
127
+ return String(fileAdapter.join(__dirname, 'data'));
121
128
  }
122
129
  /**
123
130
  * Check if reference data files exist
124
131
  */
125
- function hasReferenceData() {
132
+ function hasReferenceData(fileAdapter = io_1.defaultFileAdapter) {
133
+ const { pathExists, join } = fileAdapter;
126
134
  const dataPath = getReferenceDataPath();
127
135
  const requiredFiles = [
128
136
  'core_lists.en.json',
@@ -131,5 +139,5 @@ function hasReferenceData() {
131
139
  'synonyms.en.json',
132
140
  'fringe.en.json',
133
141
  ];
134
- return requiredFiles.every((file) => (0, io_1.getFs)().existsSync((0, io_1.getPath)().join(dataPath, file)));
142
+ return requiredFiles.every((file) => pathExists(join(dataPath, file)));
135
143
  }
@@ -1,10 +1,12 @@
1
+ import { FileAdapter } from '../utils/io';
1
2
  export declare abstract class SymbolExtractor {
2
3
  abstract getSymbolReferences(filePath: string): string[];
3
4
  }
4
5
  export declare abstract class SymbolResolver {
5
6
  protected symbolPath: string;
6
7
  protected dbPath: string;
7
- constructor(symbolPath: string, dbPath: string);
8
+ protected fileAdapter: FileAdapter;
9
+ constructor(symbolPath: string, dbPath: string, fileAdapter?: FileAdapter);
8
10
  abstract resolveSymbol(symbolRef: string): string | null;
9
11
  }
10
12
  export declare class SnapSymbolExtractor extends SymbolExtractor {
@@ -25,4 +27,4 @@ export declare class TouchChatSymbolExtractor extends SymbolExtractor {
25
27
  export declare class TouchChatSymbolResolver extends SymbolResolver {
26
28
  resolveSymbol(symbolRef: string): string | null;
27
29
  }
28
- export declare function resolveSymbol(label: string, symbolDir: string): string | null;
30
+ export declare function resolveSymbol(label: string, symbolDir: string, fileAdapter?: FileAdapter): string | null;