@willwade/aac-processors 0.1.21 → 0.2.0

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 (85) hide show
  1. package/dist/browser/processors/applePanelsProcessor.js +24 -24
  2. package/dist/browser/processors/astericsGridProcessor.js +22 -24
  3. package/dist/browser/processors/dotProcessor.js +6 -10
  4. package/dist/browser/processors/gridset/helpers.js +33 -30
  5. package/dist/browser/processors/gridset/symbolExtractor.js +2 -2
  6. package/dist/browser/processors/gridset/symbolSearch.js +22 -22
  7. package/dist/browser/processors/gridset/symbols.js +14 -14
  8. package/dist/browser/processors/gridsetProcessor.js +7 -7
  9. package/dist/browser/processors/obfProcessor.js +24 -25
  10. package/dist/browser/processors/opmlProcessor.js +6 -10
  11. package/dist/browser/processors/snap/helpers.js +34 -30
  12. package/dist/browser/processors/snapProcessor.js +28 -28
  13. package/dist/browser/processors/touchchatProcessor.js +24 -25
  14. package/dist/browser/utilities/analytics/history.js +24 -18
  15. package/dist/browser/utilities/analytics/metrics/comparison.js +16 -16
  16. package/dist/browser/utilities/analytics/metrics/vocabulary.js +2 -2
  17. package/dist/browser/utilities/analytics/reference/browser.js +16 -16
  18. package/dist/browser/utilities/analytics/reference/index.js +25 -24
  19. package/dist/browser/utils/io.js +29 -25
  20. package/dist/browser/utils/sqlite.js +5 -5
  21. package/dist/browser/utils/zip.js +2 -4
  22. package/dist/browser/validation/gridsetValidator.js +2 -2
  23. package/dist/browser/validation/obfValidator.js +2 -2
  24. package/dist/browser/validation/snapValidator.js +3 -3
  25. package/dist/browser/validation/touchChatValidator.js +3 -3
  26. package/dist/cli/index.js +19 -16
  27. package/dist/core/baseProcessor.d.ts +1 -1
  28. package/dist/processors/applePanelsProcessor.js +24 -24
  29. package/dist/processors/astericsGridProcessor.d.ts +4 -4
  30. package/dist/processors/astericsGridProcessor.js +22 -24
  31. package/dist/processors/dotProcessor.js +6 -10
  32. package/dist/processors/excelProcessor.d.ts +3 -3
  33. package/dist/processors/excelProcessor.js +10 -13
  34. package/dist/processors/gridset/helpers.d.ts +9 -9
  35. package/dist/processors/gridset/helpers.js +33 -30
  36. package/dist/processors/gridset/symbolExtractor.d.ts +1 -1
  37. package/dist/processors/gridset/symbolExtractor.js +2 -2
  38. package/dist/processors/gridset/symbolSearch.d.ts +10 -10
  39. package/dist/processors/gridset/symbolSearch.js +22 -22
  40. package/dist/processors/gridset/symbols.d.ts +3 -3
  41. package/dist/processors/gridset/symbols.js +14 -14
  42. package/dist/processors/gridsetProcessor.d.ts +2 -2
  43. package/dist/processors/gridsetProcessor.js +7 -7
  44. package/dist/processors/obfProcessor.d.ts +2 -2
  45. package/dist/processors/obfProcessor.js +24 -25
  46. package/dist/processors/obfsetProcessor.js +1 -2
  47. package/dist/processors/opmlProcessor.js +6 -10
  48. package/dist/processors/snap/helpers.d.ts +8 -8
  49. package/dist/processors/snap/helpers.js +34 -30
  50. package/dist/processors/snapProcessor.d.ts +2 -2
  51. package/dist/processors/snapProcessor.js +28 -28
  52. package/dist/processors/touchchatProcessor.d.ts +2 -2
  53. package/dist/processors/touchchatProcessor.js +24 -25
  54. package/dist/types/aac.d.ts +2 -2
  55. package/dist/utilities/analytics/history.d.ts +8 -8
  56. package/dist/utilities/analytics/history.js +24 -18
  57. package/dist/utilities/analytics/index.d.ts +1 -1
  58. package/dist/utilities/analytics/index.js +3 -2
  59. package/dist/utilities/analytics/metrics/comparison.d.ts +1 -1
  60. package/dist/utilities/analytics/metrics/comparison.js +16 -16
  61. package/dist/utilities/analytics/metrics/vocabulary.d.ts +1 -1
  62. package/dist/utilities/analytics/metrics/vocabulary.js +2 -2
  63. package/dist/utilities/analytics/reference/browser.d.ts +9 -9
  64. package/dist/utilities/analytics/reference/browser.js +16 -16
  65. package/dist/utilities/analytics/reference/index.d.ts +21 -21
  66. package/dist/utilities/analytics/reference/index.js +25 -24
  67. package/dist/utilities/symbolTools.d.ts +5 -5
  68. package/dist/utilities/symbolTools.js +10 -8
  69. package/dist/utils/io.d.ts +11 -11
  70. package/dist/utils/io.js +29 -25
  71. package/dist/utils/sqlite.d.ts +1 -1
  72. package/dist/utils/sqlite.js +5 -5
  73. package/dist/utils/zip.js +2 -4
  74. package/dist/validation/applePanelsValidator.js +7 -6
  75. package/dist/validation/astericsValidator.js +2 -2
  76. package/dist/validation/dotValidator.js +2 -2
  77. package/dist/validation/excelValidator.js +2 -2
  78. package/dist/validation/gridsetValidator.js +2 -2
  79. package/dist/validation/index.js +2 -2
  80. package/dist/validation/obfValidator.js +2 -2
  81. package/dist/validation/obfsetValidator.js +2 -2
  82. package/dist/validation/opmlValidator.js +2 -2
  83. package/dist/validation/snapValidator.js +3 -3
  84. package/dist/validation/touchChatValidator.js +3 -3
  85. package/package.json +1 -1
@@ -65,7 +65,6 @@ class SnapProcessor extends BaseProcessor {
65
65
  }
66
66
  async loadIntoTree(filePathOrBuffer) {
67
67
  const { writeBinaryToPath, removePath, mkTempDir, basename, join } = this.options.fileAdapter;
68
- await Promise.resolve();
69
68
  const tree = new AACTree();
70
69
  let dbResult = null;
71
70
  let cleanupTempZip = null;
@@ -76,22 +75,22 @@ class SnapProcessor extends BaseProcessor {
76
75
  const fileName = basename(filePathOrBuffer).toLowerCase();
77
76
  if (fileName.endsWith('.sub.zip') || filePathOrBuffer.endsWith('.sub')) {
78
77
  // Extract .sub.zip to find the embedded .sps file
79
- const tempDir = mkTempDir('snap-sub-');
78
+ const tempDir = await mkTempDir('snap-sub-');
80
79
  const zip = await this.options.zipAdapter(filePathOrBuffer);
81
80
  // Find the .sps file in the archive
82
81
  const files = zip.listFiles();
83
82
  const spsFile = files.find((f) => f.endsWith('.sps'));
84
83
  if (!spsFile) {
85
- removePath(tempDir, { recursive: true, force: true });
84
+ await removePath(tempDir, { recursive: true, force: true });
86
85
  throw new Error('No .sps file found in .sub.zip archive');
87
86
  }
88
87
  // Extract the .sps file
89
88
  const spsData = await zip.readFile(spsFile);
90
89
  const extractedSpsPath = join(tempDir, basename(spsFile));
91
- writeBinaryToPath(extractedSpsPath, Buffer.from(spsData));
90
+ await writeBinaryToPath(extractedSpsPath, Buffer.from(spsData));
92
91
  inputFile = extractedSpsPath;
93
- cleanupTempZip = () => {
94
- removePath(tempDir, { recursive: true, force: true });
92
+ cleanupTempZip = async () => {
93
+ await removePath(tempDir, { recursive: true, force: true });
95
94
  };
96
95
  }
97
96
  }
@@ -683,7 +682,7 @@ class SnapProcessor extends BaseProcessor {
683
682
  }
684
683
  finally {
685
684
  if (dbResult?.cleanup) {
686
- dbResult.cleanup();
685
+ await dbResult.cleanup();
687
686
  }
688
687
  else if (dbResult?.db) {
689
688
  dbResult.db.close();
@@ -691,7 +690,7 @@ class SnapProcessor extends BaseProcessor {
691
690
  // Clean up temporary extracted .sps file from .sub.zip
692
691
  if (cleanupTempZip) {
693
692
  try {
694
- cleanupTempZip();
693
+ await cleanupTempZip();
695
694
  }
696
695
  catch (e) {
697
696
  console.warn('[SnapProcessor] Failed to clean up temporary .sps file:', e);
@@ -707,13 +706,14 @@ class SnapProcessor extends BaseProcessor {
707
706
  if (typeof filePathOrBuffer === 'string') {
708
707
  const inputPath = filePathOrBuffer;
709
708
  const outputDir = dirname(outputPath);
710
- if (!pathExists(outputDir)) {
711
- mkDir(outputDir, { recursive: true });
709
+ const dirExists = await pathExists(outputDir);
710
+ if (!dirExists) {
711
+ await mkDir(outputDir, { recursive: true });
712
712
  }
713
- if (pathExists(outputPath)) {
714
- removePath(outputPath);
713
+ if (await pathExists(outputPath)) {
714
+ await removePath(outputPath);
715
715
  }
716
- writeBinaryToPath(outputPath, readBinaryFromInput(inputPath));
716
+ await writeBinaryToPath(outputPath, await readBinaryFromInput(inputPath));
717
717
  const Database = requireBetterSqlite3();
718
718
  const db = new Database(outputPath, { readonly: false });
719
719
  try {
@@ -775,7 +775,7 @@ class SnapProcessor extends BaseProcessor {
775
775
  finally {
776
776
  db.close();
777
777
  }
778
- return readBinaryFromInput(outputPath);
778
+ return await readBinaryFromInput(outputPath);
779
779
  }
780
780
  // Fallback for buffer inputs: rebuild from tree (may drop Snap assets)
781
781
  const tree = await this.loadIntoTree(filePathOrBuffer);
@@ -802,20 +802,20 @@ class SnapProcessor extends BaseProcessor {
802
802
  });
803
803
  });
804
804
  await this.saveFromTree(tree, outputPath);
805
- return readBinaryFromInput(outputPath);
805
+ return await readBinaryFromInput(outputPath);
806
806
  }
807
807
  async saveFromTree(tree, outputPath) {
808
808
  const { pathExists, mkDir, removePath, dirname } = this.options.fileAdapter;
809
809
  if (!isNodeRuntime()) {
810
810
  throw new Error('saveFromTree is only supported in Node.js environments for Snap files.');
811
811
  }
812
- await Promise.resolve();
813
812
  const outputDir = dirname(outputPath);
814
- if (!pathExists(outputDir)) {
815
- mkDir(outputDir, { recursive: true });
813
+ const dirExists = await pathExists(outputDir);
814
+ if (!dirExists) {
815
+ await mkDir(outputDir, { recursive: true });
816
816
  }
817
- if (pathExists(outputPath)) {
818
- removePath(outputPath);
817
+ if (await pathExists(outputPath)) {
818
+ await removePath(outputPath);
819
819
  }
820
820
  // Create a new SQLite database for Snap format
821
821
  const Database = requireBetterSqlite3();
@@ -1050,7 +1050,6 @@ class SnapProcessor extends BaseProcessor {
1050
1050
  * Add audio recording to a button in the database
1051
1051
  */
1052
1052
  async addAudioToButton(dbPath, buttonId, audioData, metadata) {
1053
- await Promise.resolve();
1054
1053
  if (!isNodeRuntime()) {
1055
1054
  throw new Error('addAudioToButton is only supported in Node.js environments.');
1056
1055
  }
@@ -1088,7 +1087,7 @@ class SnapProcessor extends BaseProcessor {
1088
1087
  const updateButton = db.prepare('UPDATE Button SET MessageRecordingId = ?, UseMessageRecording = 1, SerializedMessageSoundMetadata = ? WHERE Id = ?');
1089
1088
  const metadataJson = metadata ? JSON.stringify({ FileName: metadata }) : null;
1090
1089
  updateButton.run(audioId, metadataJson, buttonId);
1091
- return audioId;
1090
+ return Promise.resolve(audioId);
1092
1091
  }
1093
1092
  finally {
1094
1093
  db.close();
@@ -1103,7 +1102,7 @@ class SnapProcessor extends BaseProcessor {
1103
1102
  throw new Error('createAudioEnhancedPageset is only supported in Node.js environments.');
1104
1103
  }
1105
1104
  // Copy the source database to target
1106
- writeBinaryToPath(targetDbPath, readBinaryFromInput(sourceDbPath));
1105
+ await writeBinaryToPath(targetDbPath, await readBinaryFromInput(sourceDbPath));
1107
1106
  // Add audio recordings to the copy
1108
1107
  for (const [buttonId, audioInfo] of audioMappings.entries()) {
1109
1108
  await this.addAudioToButton(targetDbPath, buttonId, audioInfo.audioData, audioInfo.metadata);
@@ -1171,16 +1170,16 @@ class SnapProcessor extends BaseProcessor {
1171
1170
  * Get available PageLayouts for a Snap file
1172
1171
  * Useful for UI components that want to let users select layout size
1173
1172
  * @param filePath - Path to the Snap file
1174
- * @returns Array of available PageLayouts with their dimensions
1173
+ * @returns Promise resolving to available PageLayouts with their dimensions
1175
1174
  */
1176
- getAvailablePageLayouts(filePath) {
1175
+ async getAvailablePageLayouts(filePath) {
1177
1176
  const { writeBinaryToPath, removePath, pathExists, join } = this.options.fileAdapter;
1178
1177
  if (!isNodeRuntime()) {
1179
1178
  throw new Error('getAvailablePageLayouts is only supported in Node.js environments.');
1180
1179
  }
1181
1180
  const dbPath = typeof filePath === 'string' ? filePath : join(process.cwd(), 'temp.spb');
1182
1181
  if (Buffer.isBuffer(filePath)) {
1183
- writeBinaryToPath(dbPath, filePath);
1182
+ await writeBinaryToPath(dbPath, filePath);
1184
1183
  }
1185
1184
  let db = null;
1186
1185
  try {
@@ -1231,9 +1230,10 @@ class SnapProcessor extends BaseProcessor {
1231
1230
  db.close();
1232
1231
  }
1233
1232
  // Clean up temporary file if created from buffer
1234
- if (Buffer.isBuffer(filePath) && pathExists(dbPath)) {
1233
+ const exists = await pathExists(dbPath);
1234
+ if (Buffer.isBuffer(filePath) && exists) {
1235
1235
  try {
1236
- removePath(dbPath);
1236
+ await removePath(dbPath);
1237
1237
  }
1238
1238
  catch (e) {
1239
1239
  console.warn('Failed to clean up temporary file:', e);
@@ -56,7 +56,6 @@ class TouchChatProcessor extends BaseProcessor {
56
56
  }
57
57
  async loadIntoTree(filePathOrBuffer) {
58
58
  const { readBinaryFromInput } = this.options.fileAdapter;
59
- await Promise.resolve();
60
59
  // Unzip .ce file, extract the .c4v SQLite DB, and parse pages/buttons
61
60
  let db = null;
62
61
  let cleanup;
@@ -64,7 +63,7 @@ class TouchChatProcessor extends BaseProcessor {
64
63
  // Store source file path or buffer
65
64
  this.sourceFile = filePathOrBuffer;
66
65
  // Step 1: Unzip
67
- const zipInput = readBinaryFromInput(filePathOrBuffer);
66
+ const zipInput = await readBinaryFromInput(filePathOrBuffer);
68
67
  const zip = await this.options.zipAdapter(zipInput);
69
68
  const vocabEntry = zip.listFiles().find((name) => name.endsWith('.c4v'));
70
69
  if (!vocabEntry) {
@@ -469,7 +468,7 @@ class TouchChatProcessor extends BaseProcessor {
469
468
  finally {
470
469
  // Clean up
471
470
  if (cleanup) {
472
- cleanup();
471
+ await cleanup();
473
472
  }
474
473
  else if (db) {
475
474
  db.close();
@@ -492,11 +491,12 @@ class TouchChatProcessor extends BaseProcessor {
492
491
  if (typeof filePathOrBuffer === 'string') {
493
492
  const inputPath = filePathOrBuffer;
494
493
  const outputDir = dirname(outputPath);
495
- if (!pathExists(outputDir)) {
496
- mkDir(outputDir, { recursive: true });
494
+ const dirExists = await pathExists(outputDir);
495
+ if (!dirExists) {
496
+ await mkDir(outputDir, { recursive: true });
497
497
  }
498
- if (pathExists(outputPath)) {
499
- removePath(outputPath);
498
+ if (await pathExists(outputPath)) {
499
+ await removePath(outputPath);
500
500
  }
501
501
  const zip = await this.options.zipAdapter(inputPath);
502
502
  const entries = getZipEntriesFromAdapter(zip);
@@ -504,10 +504,10 @@ class TouchChatProcessor extends BaseProcessor {
504
504
  if (!vocabEntry) {
505
505
  throw new Error('No .c4v vocab DB found in TouchChat export');
506
506
  }
507
- const tempDir = mkTempDir('touchchat-translate-');
507
+ const tempDir = await mkTempDir('touchchat-translate-');
508
508
  const dbPath = join(tempDir, 'vocab.c4v');
509
509
  try {
510
- writeBinaryToPath(dbPath, await vocabEntry.getData());
510
+ await writeBinaryToPath(dbPath, await vocabEntry.getData());
511
511
  const Database = requireBetterSqlite3();
512
512
  const db = new Database(dbPath, { readonly: false });
513
513
  try {
@@ -574,20 +574,20 @@ class TouchChatProcessor extends BaseProcessor {
574
574
  }
575
575
  files.push({
576
576
  name: vocabEntry.entryName,
577
- data: readBinaryFromInput(dbPath),
577
+ data: await readBinaryFromInput(dbPath),
578
578
  });
579
579
  const zipData = await outputZip.writeFiles(files);
580
- writeBinaryToPath(outputPath, zipData);
580
+ await writeBinaryToPath(outputPath, zipData);
581
581
  }
582
582
  finally {
583
583
  try {
584
- removePath(tempDir, { recursive: true, force: true });
584
+ await removePath(tempDir, { recursive: true, force: true });
585
585
  }
586
586
  catch {
587
587
  // Best-effort cleanup
588
588
  }
589
589
  }
590
- return readBinaryFromInput(outputPath);
590
+ return await readBinaryFromInput(outputPath);
591
591
  }
592
592
  // Fallback for buffer inputs: rebuild from tree (may drop TouchChat metadata)
593
593
  const tree = await this.loadIntoTree(filePathOrBuffer);
@@ -614,16 +614,15 @@ class TouchChatProcessor extends BaseProcessor {
614
614
  });
615
615
  });
616
616
  await this.saveFromTree(tree, outputPath);
617
- return readBinaryFromInput(outputPath);
617
+ return await readBinaryFromInput(outputPath);
618
618
  }
619
619
  async saveFromTree(tree, outputPath) {
620
620
  const { writeBinaryToPath, mkTempDir, readBinaryFromInput, pathExists, removePath, join } = this.options.fileAdapter;
621
- await Promise.resolve();
622
621
  if (!isNodeRuntime()) {
623
622
  throw new Error('saveFromTree is only supported in Node.js environments for TouchChat files.');
624
623
  }
625
624
  // Create a TouchChat database that matches the expected schema for loading
626
- const tmpDir = mkTempDir('touchchat-export-');
625
+ const tmpDir = await mkTempDir('touchchat-export-');
627
626
  const dbPath = join(tmpDir, 'vocab.c4v');
628
627
  try {
629
628
  const Database = requireBetterSqlite3();
@@ -918,19 +917,19 @@ class TouchChatProcessor extends BaseProcessor {
918
917
  db.close();
919
918
  // Create zip file with the database
920
919
  const zip = await this.options.zipAdapter();
921
- const data = readBinaryFromInput(dbPath);
920
+ const data = await readBinaryFromInput(dbPath);
922
921
  const zipData = await zip.writeFiles([
923
922
  {
924
923
  name: 'vocab.c4v',
925
924
  data,
926
925
  },
927
926
  ]);
928
- writeBinaryToPath(outputPath, zipData);
927
+ await writeBinaryToPath(outputPath, zipData);
929
928
  }
930
929
  finally {
931
930
  // Clean up
932
- if (pathExists(tmpDir)) {
933
- removePath(tmpDir, { recursive: true, force: true });
931
+ if (await pathExists(tmpDir)) {
932
+ await removePath(tmpDir, { recursive: true, force: true });
934
933
  }
935
934
  }
936
935
  }
@@ -1013,7 +1012,7 @@ class TouchChatProcessor extends BaseProcessor {
1013
1012
  const outputPath = filePath.replace(/\.ce$/, '_translated.ce');
1014
1013
  // Use existing processTexts method
1015
1014
  await this.processTexts(filePath, translations, outputPath);
1016
- return Promise.resolve(outputPath);
1015
+ return outputPath;
1017
1016
  }
1018
1017
  catch (error) {
1019
1018
  return Promise.reject(new Error(`Failed to generate translated download: ${error instanceof Error ? error.message : 'Unknown error'}`));
@@ -1025,7 +1024,7 @@ class TouchChatProcessor extends BaseProcessor {
1025
1024
  * @returns Promise with validation result
1026
1025
  */
1027
1026
  async validate(filePath) {
1028
- return TouchChatValidator.validateFile(filePath, this.options.fileAdapter);
1027
+ return await TouchChatValidator.validateFile(filePath, this.options.fileAdapter);
1029
1028
  }
1030
1029
  /**
1031
1030
  * Extract symbol information from a TouchChat file for LLM-based translation.
@@ -1034,7 +1033,7 @@ class TouchChatProcessor extends BaseProcessor {
1034
1033
  * This method uses shared translation utilities that work across all AAC formats.
1035
1034
  *
1036
1035
  * @param filePathOrBuffer - Path to TouchChat .ce file or buffer
1037
- * @returns Array of symbol information for LLM processing
1036
+ * @returns Promise resolving to symbol information for LLM processing
1038
1037
  */
1039
1038
  async extractSymbolsForLLM(filePathOrBuffer) {
1040
1039
  const tree = await this.loadIntoTree(filePathOrBuffer);
@@ -1064,7 +1063,7 @@ class TouchChatProcessor extends BaseProcessor {
1064
1063
  * @param llmTranslations - Array of LLM translations with symbol info
1065
1064
  * @param outputPath - Where to save the translated TouchChat file
1066
1065
  * @param options - Translation options (e.g., allowPartial for testing)
1067
- * @returns Buffer of the translated TouchChat file
1066
+ * @returns Promise resolving to a buffer of the translated TouchChat file
1068
1067
  */
1069
1068
  async processLLMTranslations(filePathOrBuffer, llmTranslations, outputPath, options) {
1070
1069
  const { readBinaryFromInput } = this.options.fileAdapter;
@@ -1109,7 +1108,7 @@ class TouchChatProcessor extends BaseProcessor {
1109
1108
  });
1110
1109
  // Save and return
1111
1110
  await this.saveFromTree(tree, outputPath);
1112
- return readBinaryFromInput(outputPath);
1111
+ return await readBinaryFromInput(outputPath);
1113
1112
  }
1114
1113
  }
1115
1114
  export { TouchChatProcessor };
@@ -60,8 +60,9 @@ export function exportHistoryToBaton(entries, options) {
60
60
  /**
61
61
  * Read Grid 3 phrase history from a history.sqlite database and tag entries with their source.
62
62
  */
63
- export function readGrid3History(historyDbPath) {
64
- return readGrid3HistoryImpl(historyDbPath).map((e) => ({
63
+ export async function readGrid3History(historyDbPath) {
64
+ const history = await readGrid3HistoryImpl(historyDbPath);
65
+ return history.map((e) => ({
65
66
  ...e,
66
67
  source: 'Grid',
67
68
  }));
@@ -69,8 +70,9 @@ export function readGrid3History(historyDbPath) {
69
70
  /**
70
71
  * Read Grid 3 history for a specific user/language combination.
71
72
  */
72
- export function readGrid3HistoryForUser(userName, langCode) {
73
- return readGrid3HistoryForUserImpl(userName, langCode).map((e) => ({
73
+ export async function readGrid3HistoryForUser(userName, langCode) {
74
+ const history = await readGrid3HistoryForUserImpl(userName, langCode);
75
+ return history.map((e) => ({
74
76
  ...e,
75
77
  source: 'Grid',
76
78
  }));
@@ -78,39 +80,43 @@ export function readGrid3HistoryForUser(userName, langCode) {
78
80
  /**
79
81
  * Read every available Grid 3 history database on the machine.
80
82
  */
81
- export function readAllGrid3History() {
82
- return readAllGrid3HistoryImpl().map((e) => ({ ...e, source: 'Grid' }));
83
+ export async function readAllGrid3History() {
84
+ const history = await readAllGrid3HistoryImpl();
85
+ return history.map((e) => ({ ...e, source: 'Grid' }));
83
86
  }
84
87
  /**
85
88
  * Read Snap button usage from a pageset database and tag entries with source.
86
89
  */
87
- export function readSnapUsage(pagesetPath) {
88
- return readSnapUsageImpl(pagesetPath).map((e) => ({ ...e, source: 'Snap' }));
90
+ export async function readSnapUsage(pagesetPath) {
91
+ const usage = await readSnapUsageImpl(pagesetPath);
92
+ return usage.map((e) => ({ ...e, source: 'Snap' }));
89
93
  }
90
94
  /**
91
95
  * Read Snap usage for a specific user across all discovered pagesets.
92
96
  */
93
- export function readSnapUsageForUser(userId, packageNamePattern = 'TobiiDynavox') {
94
- return readSnapUsageForUserImpl(userId, packageNamePattern).map((e) => ({
97
+ export async function readSnapUsageForUser(userId, packageNamePattern = 'TobiiDynavox') {
98
+ const usage = await readSnapUsageForUserImpl(userId, packageNamePattern);
99
+ return usage.map((e) => ({
95
100
  ...e,
96
101
  source: 'Snap',
97
102
  }));
98
103
  }
99
- export function listSnapUsers() {
100
- return findSnapUsers();
104
+ export async function listSnapUsers() {
105
+ return await findSnapUsers();
101
106
  }
102
107
  /**
103
108
  * List Grid 3 users on the current machine.
104
109
  */
105
- export function listGrid3Users() {
106
- return findGrid3Users();
110
+ export async function listGrid3Users() {
111
+ return await findGrid3Users();
107
112
  }
108
113
  /**
109
114
  * Convenience helper to gather all available history across Grid 3 and Snap.
110
115
  * Returns an empty array if no history files are present.
111
116
  */
112
- export function collectUnifiedHistory() {
113
- const gridHistory = readAllGrid3History();
114
- const snapHistory = findSnapUsers().flatMap((u) => readSnapUsageForUser(u.userId));
115
- return [...gridHistory, ...snapHistory];
117
+ export async function collectUnifiedHistory() {
118
+ const gridHistory = await readAllGrid3History();
119
+ const users = await findSnapUsers();
120
+ const snapHistory = await Promise.all(users.map(async (u) => await readSnapUsageForUser(u.userId)));
121
+ return [...gridHistory, ...snapHistory.flat()];
116
122
  }
@@ -23,7 +23,7 @@ export class ComparisonAnalyzer {
23
23
  /**
24
24
  * Compare two board sets
25
25
  */
26
- compare(targetResult, compareResult, options) {
26
+ async compare(targetResult, compareResult, options) {
27
27
  // Create base result from target
28
28
  const baseResult = { ...targetResult };
29
29
  // Create word maps with normalized keys
@@ -77,7 +77,7 @@ export class ComparisonAnalyzer {
77
77
  };
78
78
  });
79
79
  // Calculate CARE components
80
- const careComponents = this.calculateCareComponents(targetResult, compareResult, overlappingWords, options);
80
+ const careComponents = await this.calculateCareComponents(targetResult, compareResult, overlappingWords, options);
81
81
  // Analyze high/low effort words
82
82
  const highEffortWords = [];
83
83
  const lowEffortWords = [];
@@ -110,7 +110,7 @@ export class ComparisonAnalyzer {
110
110
  // Sentence analysis
111
111
  let sentences = [];
112
112
  if (options?.includeSentences) {
113
- const testSentences = this.referenceLoader.loadSentences();
113
+ const testSentences = await this.referenceLoader.loadSentences();
114
114
  const targetSentences = this.sentenceAnalyzer.analyzeSentences(targetResult, testSentences);
115
115
  const compareSentences = this.sentenceAnalyzer.analyzeSentences(compareResult, testSentences);
116
116
  sentences = targetSentences.map((ts, idx) => ({
@@ -123,7 +123,7 @@ export class ComparisonAnalyzer {
123
123
  }));
124
124
  }
125
125
  // Core vocabulary analysis
126
- const coreLists = this.referenceLoader.loadCoreLists();
126
+ const coreLists = await this.referenceLoader.loadCoreLists();
127
127
  const cores = {};
128
128
  coreLists.forEach((list) => {
129
129
  let targetTotal = 0;
@@ -171,8 +171,8 @@ export class ComparisonAnalyzer {
171
171
  }
172
172
  });
173
173
  // Fringe vocabulary analysis
174
- const fringeWords = this.analyzeFringe(targetWords, compareWords);
175
- const commonFringeWords = this.analyzeCommonFringe(targetWords, compareWords);
174
+ const fringeWords = await this.analyzeFringe(targetWords, compareWords);
175
+ const commonFringeWords = await this.analyzeCommonFringe(targetWords, compareWords);
176
176
  return {
177
177
  ...baseResult,
178
178
  buttons: enrichedButtons,
@@ -214,9 +214,9 @@ export class ComparisonAnalyzer {
214
214
  /**
215
215
  * Calculate CARE component scores
216
216
  */
217
- calculateCareComponents(targetResult, compareResult, _overlappingWords, options) {
217
+ async calculateCareComponents(targetResult, compareResult, _overlappingWords, options) {
218
218
  // Load common words with baseline efforts (matching Ruby line 527-534)
219
- const commonWordsData = this.referenceLoader.loadCommonWords();
219
+ const commonWordsData = await this.referenceLoader.loadCommonWords();
220
220
  const commonWords = new Map();
221
221
  commonWordsData.words.forEach((word) => {
222
222
  commonWords.set(word.toLowerCase(), commonWordsData.efforts[word] || 0);
@@ -268,10 +268,10 @@ export class ComparisonAnalyzer {
268
268
  }
269
269
  });
270
270
  // Load reference data
271
- const coreLists = this.referenceLoader.loadCoreLists();
272
- const fringe = this.referenceLoader.loadFringe();
273
- const commonFringe = this.referenceLoader.loadCommonFringe();
274
- const sentences = this.referenceLoader.loadSentences();
271
+ const coreLists = await this.referenceLoader.loadCoreLists();
272
+ const fringe = await this.referenceLoader.loadFringe();
273
+ const commonFringe = await this.referenceLoader.loadCommonFringe();
274
+ const sentences = await this.referenceLoader.loadSentences();
275
275
  // Calculate core coverage and effort (matching Ruby lines 609-647)
276
276
  let coreCount = 0;
277
277
  let compCoreCount = 0;
@@ -423,8 +423,8 @@ export class ComparisonAnalyzer {
423
423
  /**
424
424
  * Analyze fringe vocabulary
425
425
  */
426
- analyzeFringe(targetWords, compareWords) {
427
- const fringe = this.referenceLoader.loadFringe();
426
+ async analyzeFringe(targetWords, compareWords) {
427
+ const fringe = await this.referenceLoader.loadFringe();
428
428
  const result = [];
429
429
  fringe.forEach((word) => {
430
430
  const key = this.normalize(word);
@@ -444,8 +444,8 @@ export class ComparisonAnalyzer {
444
444
  /**
445
445
  * Analyze common fringe vocabulary
446
446
  */
447
- analyzeCommonFringe(targetWords, compareWords) {
448
- const fringe = this.referenceLoader.loadFringe();
447
+ async analyzeCommonFringe(targetWords, compareWords) {
448
+ const fringe = await this.referenceLoader.loadFringe();
449
449
  const result = [];
450
450
  fringe.forEach((word) => {
451
451
  const key = this.normalize(word);
@@ -13,12 +13,12 @@ export class VocabularyAnalyzer {
13
13
  /**
14
14
  * Analyze vocabulary coverage against core lists
15
15
  */
16
- analyze(metrics, options) {
16
+ async analyze(metrics, options) {
17
17
  // const locale = options?.locale || metrics.locale || 'en';
18
18
  const highEffortThreshold = options?.highEffortThreshold || 5.0;
19
19
  const lowEffortThreshold = options?.lowEffortThreshold || 2.0;
20
20
  // Load reference data
21
- const coreLists = this.referenceLoader.loadCoreLists();
21
+ const coreLists = await this.referenceLoader.loadCoreLists();
22
22
  // Create word to effort map (using lowercase keys for matching)
23
23
  const wordEffortMap = new Map();
24
24
  metrics.buttons.forEach((btn) => {
@@ -5,34 +5,34 @@ export class InMemoryReferenceLoader {
5
5
  constructor(data) {
6
6
  this.data = data;
7
7
  }
8
- loadCoreLists() {
9
- return this.data.coreLists;
8
+ async loadCoreLists() {
9
+ return Promise.resolve(this.data.coreLists);
10
10
  }
11
- loadCommonWords() {
12
- return this.data.commonWords;
11
+ async loadCommonWords() {
12
+ return Promise.resolve(this.data.commonWords);
13
13
  }
14
- loadSynonyms() {
15
- return this.data.synonyms;
14
+ async loadSynonyms() {
15
+ return Promise.resolve(this.data.synonyms);
16
16
  }
17
- loadSentences() {
18
- return this.data.sentences;
17
+ async loadSentences() {
18
+ return Promise.resolve(this.data.sentences);
19
19
  }
20
- loadFringe() {
21
- return this.data.fringe;
20
+ async loadFringe() {
21
+ return Promise.resolve(this.data.fringe);
22
22
  }
23
- loadBaseWords() {
24
- return this.data.baseWords;
23
+ async loadBaseWords() {
24
+ return Promise.resolve(this.data.baseWords);
25
25
  }
26
- loadCommonFringe() {
26
+ async loadCommonFringe() {
27
27
  const commonWords = new Set(this.data.commonWords.words.map((w) => w.toLowerCase()));
28
28
  const coreWords = new Set();
29
29
  this.data.coreLists.forEach((list) => {
30
30
  list.words.forEach((word) => coreWords.add(word.toLowerCase()));
31
31
  });
32
- return Array.from(commonWords).filter((word) => !coreWords.has(word));
32
+ return Promise.resolve(Array.from(commonWords).filter((word) => !coreWords.has(word)));
33
33
  }
34
- loadAll() {
35
- return this.data;
34
+ async loadAll() {
35
+ return Promise.resolve(this.data);
36
36
  }
37
37
  }
38
38
  export async function loadReferenceDataFromUrl(baseUrl, locale = 'en') {