diff --git a/.cspell-wordlist.txt b/.cspell-wordlist.txt index 283af1481..c0c708edf 100644 --- a/.cspell-wordlist.txt +++ b/.cspell-wordlist.txt @@ -95,6 +95,7 @@ Português codegen cstdint ocurred +RNFS libfbjni libc gradlew @@ -104,5 +105,8 @@ POTTEDPLANT TVMONITOR sublist TTFT +pogodin +kesha +antonov timestamping logprob diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 7989ebb5d..dd9585bc2 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -24,7 +24,7 @@ jobs: run: yarn lint - name: Typecheck files - run: yarn typecheck + run: yarn workspaces foreach --all --topological-dev run prepare && yarn typecheck build-library: runs-on: ubuntu-latest @@ -35,7 +35,5 @@ jobs: - name: Setup uses: ./.github/actions/setup - - name: Build package - run: | - cd packages/react-native-executorch - yarn prepare + - name: Build all packages + run: yarn workspaces foreach --all --topological-dev run prepare diff --git a/.nvmrc b/.nvmrc index 9a2a0e219..53d1c14db 100644 --- a/.nvmrc +++ b/.nvmrc @@ -1 +1 @@ -v20 +v22 diff --git a/apps/computer-vision/app/_layout.tsx b/apps/computer-vision/app/_layout.tsx index 5914d2fe8..35fba7fb1 100644 --- a/apps/computer-vision/app/_layout.tsx +++ b/apps/computer-vision/app/_layout.tsx @@ -1,4 +1,7 @@ import { Drawer } from 'expo-router/drawer'; +import { initExecutorch } from 'react-native-executorch'; +import { ExpoResourceFetcher } from '@react-native-executorch/expo-resource-fetcher'; + import ColorPalette from '../colors'; import React, { useState } from 'react'; import { Text, StyleSheet, View } from 'react-native'; @@ -10,6 +13,10 @@ import { } from '@react-navigation/drawer'; import { GeneratingContext } from '../context'; +initExecutorch({ + resourceFetcher: ExpoResourceFetcher, +}); + interface CustomDrawerProps extends DrawerContentComponentProps { isGenerating: boolean; } diff --git a/apps/computer-vision/package.json b/apps/computer-vision/package.json index 63885109a..cce918197 100644 --- a/apps/computer-vision/package.json +++ b/apps/computer-vision/package.json @@ -11,7 +11,8 @@ "lint": "eslint . --ext .ts,.tsx --fix" }, "dependencies": { - "@react-native/metro-config": "^0.76.3", + "@react-native-executorch/expo-resource-fetcher": "workspace:*", + "@react-native/metro-config": "^0.81.5", "@react-navigation/drawer": "^7.3.9", "@react-navigation/native": "^7.1.6", "@shopify/react-native-skia": "2.2.12", @@ -21,7 +22,7 @@ "expo-linking": "~8.0.10", "expo-router": "~6.0.17", "expo-status-bar": "~3.0.9", - "metro-config": "^0.81.0", + "metro-config": "^0.81.5", "react": "19.1.0", "react-native": "0.81.5", "react-native-device-info": "^14.0.4", diff --git a/apps/computer-vision/tsconfig.json b/apps/computer-vision/tsconfig.json index 47026ce43..a08f2140a 100644 --- a/apps/computer-vision/tsconfig.json +++ b/apps/computer-vision/tsconfig.json @@ -9,7 +9,10 @@ "customConditions": ["react-native"], "noEmit": true, "paths": { - "react-native-executorch": ["../../packages/react-native-executorch/src"] + "react-native-executorch": ["../../packages/react-native-executorch/src"], + "@react-native-executorch/expo-resource-fetcher": [ + "../../packages/expo-resource-fetcher/src" + ] } } } diff --git a/apps/llm/app/_layout.tsx b/apps/llm/app/_layout.tsx index 68c715a80..5ece80f1f 100644 --- a/apps/llm/app/_layout.tsx +++ b/apps/llm/app/_layout.tsx @@ -1,8 +1,9 @@ import { Drawer } from 'expo-router/drawer'; +import { initExecutorch } from 'react-native-executorch'; +import { ExpoResourceFetcher } from '@react-native-executorch/expo-resource-fetcher'; import ColorPalette from '../colors'; import React, { useState } from 'react'; import { Text, StyleSheet, View } from 'react-native'; - import { DrawerContentComponentProps, DrawerContentScrollView, @@ -10,6 +11,10 @@ import { } from '@react-navigation/drawer'; import { GeneratingContext } from '../context'; +initExecutorch({ + resourceFetcher: ExpoResourceFetcher, +}); + interface CustomDrawerProps extends DrawerContentComponentProps { isGenerating: boolean; } diff --git a/apps/llm/package.json b/apps/llm/package.json index de046a299..04597d963 100644 --- a/apps/llm/package.json +++ b/apps/llm/package.json @@ -11,7 +11,8 @@ "lint": "eslint . --ext .ts,.tsx --fix" }, "dependencies": { - "@react-native/metro-config": "^0.76.3", + "@react-native-executorch/expo-resource-fetcher": "workspace:*", + "@react-native/metro-config": "^0.81.5", "@react-navigation/drawer": "^7.3.9", "@react-navigation/native": "^7.1.6", "expo": "^54.0.27", @@ -22,7 +23,7 @@ "expo-linking": "~8.0.10", "expo-router": "~6.0.17", "expo-status-bar": "~3.0.9", - "metro-config": "^0.81.0", + "metro-config": "^0.81.5", "react": "19.1.0", "react-native": "0.81.5", "react-native-audio-api": "^0.8.2", diff --git a/apps/llm/tsconfig.json b/apps/llm/tsconfig.json index 47026ce43..a08f2140a 100644 --- a/apps/llm/tsconfig.json +++ b/apps/llm/tsconfig.json @@ -9,7 +9,10 @@ "customConditions": ["react-native"], "noEmit": true, "paths": { - "react-native-executorch": ["../../packages/react-native-executorch/src"] + "react-native-executorch": ["../../packages/react-native-executorch/src"], + "@react-native-executorch/expo-resource-fetcher": [ + "../../packages/expo-resource-fetcher/src" + ] } } } diff --git a/apps/speech/App.tsx b/apps/speech/App.tsx index af0598b59..ab036678e 100644 --- a/apps/speech/App.tsx +++ b/apps/speech/App.tsx @@ -5,6 +5,12 @@ import { SpeechToTextScreen } from './screens/SpeechToTextScreen'; import ColorPalette from './colors'; import ExecutorchLogo from './assets/executorch.svg'; import { Quiz } from './screens/Quiz'; +import { initExecutorch } from 'react-native-executorch'; +import { ExpoResourceFetcher } from '@react-native-executorch/expo-resource-fetcher'; + +initExecutorch({ + resourceFetcher: ExpoResourceFetcher, +}); export default function App() { const [currentScreen, setCurrentScreen] = useState< diff --git a/apps/speech/package.json b/apps/speech/package.json index 1c0607be2..9c9fdbee1 100644 --- a/apps/speech/package.json +++ b/apps/speech/package.json @@ -11,12 +11,13 @@ "lint": "eslint . --ext .ts,.tsx --fix" }, "dependencies": { - "@react-native/metro-config": "^0.76.3", + "@react-native-executorch/expo-resource-fetcher": "workspace:*", + "@react-native/metro-config": "^0.81.5", "buffer": "^6.0.3", "expo": "^54.0.27", "expo-font": "~14.0.10", "expo-status-bar": "~3.0.9", - "metro-config": "^0.81.0", + "metro-config": "^0.81.5", "react": "19.1.0", "react-native": "0.81.5", "react-native-audio-api": "0.11.3", diff --git a/apps/speech/tsconfig.json b/apps/speech/tsconfig.json index 47026ce43..a08f2140a 100644 --- a/apps/speech/tsconfig.json +++ b/apps/speech/tsconfig.json @@ -9,7 +9,10 @@ "customConditions": ["react-native"], "noEmit": true, "paths": { - "react-native-executorch": ["../../packages/react-native-executorch/src"] + "react-native-executorch": ["../../packages/react-native-executorch/src"], + "@react-native-executorch/expo-resource-fetcher": [ + "../../packages/expo-resource-fetcher/src" + ] } } } diff --git a/apps/text-embeddings/app/_layout.tsx b/apps/text-embeddings/app/_layout.tsx index 16bf0e87a..c0633a993 100644 --- a/apps/text-embeddings/app/_layout.tsx +++ b/apps/text-embeddings/app/_layout.tsx @@ -1,4 +1,6 @@ import { Drawer } from 'expo-router/drawer'; +import { initExecutorch } from 'react-native-executorch'; +import { ExpoResourceFetcher } from '@react-native-executorch/expo-resource-fetcher'; import ColorPalette from '../colors'; import React, { useState } from 'react'; import { Text, StyleSheet, View } from 'react-native'; @@ -10,6 +12,10 @@ import { } from '@react-navigation/drawer'; import { GeneratingContext } from '../context'; +initExecutorch({ + resourceFetcher: ExpoResourceFetcher, +}); + interface CustomDrawerProps extends DrawerContentComponentProps { isGenerating: boolean; } diff --git a/apps/text-embeddings/package.json b/apps/text-embeddings/package.json index cbf0da96c..64093b919 100644 --- a/apps/text-embeddings/package.json +++ b/apps/text-embeddings/package.json @@ -14,6 +14,7 @@ "@react-navigation/native": "*" }, "dependencies": { + "@react-native-executorch/expo-resource-fetcher": "workspace:*", "@react-navigation/drawer": "^7.3.9", "expo": "^54.0.27", "expo-constants": "~18.0.11", diff --git a/apps/text-embeddings/tsconfig.json b/apps/text-embeddings/tsconfig.json index 47026ce43..a08f2140a 100644 --- a/apps/text-embeddings/tsconfig.json +++ b/apps/text-embeddings/tsconfig.json @@ -9,7 +9,10 @@ "customConditions": ["react-native"], "noEmit": true, "paths": { - "react-native-executorch": ["../../packages/react-native-executorch/src"] + "react-native-executorch": ["../../packages/react-native-executorch/src"], + "@react-native-executorch/expo-resource-fetcher": [ + "../../packages/expo-resource-fetcher/src" + ] } } } diff --git a/docs/docs/01-fundamentals/01-getting-started.md b/docs/docs/01-fundamentals/01-getting-started.md index 109f6e479..f1826c0b4 100644 --- a/docs/docs/01-fundamentals/01-getting-started.md +++ b/docs/docs/01-fundamentals/01-getting-started.md @@ -62,7 +62,7 @@ Installation is pretty straightforward, just use your favorite package manager. -If you're using bare React Native (instead of a managed Expo project), you also need to install Expo Modules because the underlying implementation relies on expo-file-system. Since expo-file-system is an Expo package, bare React Native projects need **Expo Modules** to properly integrate and use it. The link provided (https://docs.expo.dev/bare/installing-expo-modules/) offers guidance on setting up Expo Modules in a bare React Native environment. +Our library offers support for both bare React Native and Expo projects. We encourage you to use Expo project if possible. If you are planning to migrate from bare React Native to Expo project, the link (https://docs.expo.dev/bare/installing-expo-modules/) offers a guidance on setting up Expo Modules in a bare React Native environment. If you plan on using your models via require() instead of fetching them from a url, you also need to add following lines to your `metro.config.js`: diff --git a/docs/docs/01-fundamentals/02-loading-models.md b/docs/docs/01-fundamentals/02-loading-models.md index 96be9784f..ebc6e3079 100644 --- a/docs/docs/01-fundamentals/02-loading-models.md +++ b/docs/docs/01-fundamentals/02-loading-models.md @@ -4,6 +4,46 @@ title: Loading Models There are three different methods available for loading model files, depending on their size and location. +## Prerequisites + +In our library, you can use two different resource fetching mechanisms. One is implemented using Expo module, the other one uses external file-system library. We encourage you to use implementation utilizing Expo if possible. But sometimes you cannot add Expo module to your project, then you should use bare React Native implementation. + +To use initialize Expo adapter, please add these libraries: + +```bash +yarn add @rn-executorch/expo-adapter +yarn add expo-file-system expo-asset +``` + +and then add the following code in your React Native app: + +```typescript +import { initExecutorch } from 'react-native-executorch'; +import { ExpoResourceFetcher } from '@react-native-executorch/expo-resource-fetcher'; + +initExecutorch({ + resourceFetcher: ExpoResourceFetcher, +}); +``` + +If you cannot use Expo in your project, proceed with the following steps: + +```bash +yarn add @rn-executorch/bare-adapter +yarn add @dr.pogodin/react-native-fs @kesha-antonov/react-native-background-downloader +``` + +and + +```typescript +import { initExecutorch } from 'react-native-executorch'; +import { BareResourceFetcher } from '@rn-executorch/bare-adapter'; + +initExecutorch({ + resourceFetcher: BareResourceFetcher, +}); +``` + **1. Load from React Native assets folder (For Files < 512MB)** ```typescript diff --git a/docs/docs/01-fundamentals/03-frequently-asked-questions.md b/docs/docs/01-fundamentals/03-frequently-asked-questions.md index 9216c615f..69e3792d4 100644 --- a/docs/docs/01-fundamentals/03-frequently-asked-questions.md +++ b/docs/docs/01-fundamentals/03-frequently-asked-questions.md @@ -31,7 +31,7 @@ If your model doesn't support it, you can still work around it using context. Fo ### Can I use React Native ExecuTorch in bare React Native apps? -To use the library, you need to install Expo Modules first. For a setup guide, refer to [this tutorial](https://docs.expo.dev/bare/installing-expo-modules/). This is because we use Expo File System under the hood to download and manage the model binaries. +Yes, staring from version `0.8.x` you can use React Native ExecuTorch in bare React Native apps. You just need to use bare React Native resource fetcher instead of Expo one, see: [Loading models section](./02-loading-models.md) for more details. ### Do you support the old architecture? diff --git a/docs/docs/06-api-reference/index.md b/docs/docs/06-api-reference/index.md index 1415421de..debc55540 100644 --- a/docs/docs/06-api-reference/index.md +++ b/docs/docs/06-api-reference/index.md @@ -20,159 +20,159 @@ ## Models - Classification -- [EFFICIENTNET\_V2\_S](variables/EFFICIENTNET_V2_S.md) +- [EFFICIENTNET_V2_S](variables/EFFICIENTNET_V2_S.md) ## Models - Image Embeddings -- [CLIP\_VIT\_BASE\_PATCH32\_IMAGE](variables/CLIP_VIT_BASE_PATCH32_IMAGE.md) +- [CLIP_VIT_BASE_PATCH32_IMAGE](variables/CLIP_VIT_BASE_PATCH32_IMAGE.md) ## Models - Image Generation -- [BK\_SDM\_TINY\_VPRED\_256](variables/BK_SDM_TINY_VPRED_256.md) -- [BK\_SDM\_TINY\_VPRED\_512](variables/BK_SDM_TINY_VPRED_512.md) +- [BK_SDM_TINY_VPRED_256](variables/BK_SDM_TINY_VPRED_256.md) +- [BK_SDM_TINY_VPRED_512](variables/BK_SDM_TINY_VPRED_512.md) ## Models - Image Segmentation -- [DEEPLAB\_V3\_RESNET50](variables/DEEPLAB_V3_RESNET50.md) +- [DEEPLAB_V3_RESNET50](variables/DEEPLAB_V3_RESNET50.md) ## Models - LMM -- [HAMMER2\_1\_0\_5B](variables/HAMMER2_1_0_5B.md) -- [HAMMER2\_1\_0\_5B\_QUANTIZED](variables/HAMMER2_1_0_5B_QUANTIZED.md) -- [HAMMER2\_1\_1\_5B](variables/HAMMER2_1_1_5B.md) -- [HAMMER2\_1\_1\_5B\_QUANTIZED](variables/HAMMER2_1_1_5B_QUANTIZED.md) -- [HAMMER2\_1\_3B](variables/HAMMER2_1_3B.md) -- [HAMMER2\_1\_3B\_QUANTIZED](variables/HAMMER2_1_3B_QUANTIZED.md) -- [LLAMA3\_2\_1B](variables/LLAMA3_2_1B.md) -- [LLAMA3\_2\_1B\_QLORA](variables/LLAMA3_2_1B_QLORA.md) -- [LLAMA3\_2\_1B\_SPINQUANT](variables/LLAMA3_2_1B_SPINQUANT.md) -- [LLAMA3\_2\_3B](variables/LLAMA3_2_3B.md) -- [LLAMA3\_2\_3B\_QLORA](variables/LLAMA3_2_3B_QLORA.md) -- [LLAMA3\_2\_3B\_SPINQUANT](variables/LLAMA3_2_3B_SPINQUANT.md) -- [PHI\_4\_MINI\_4B](variables/PHI_4_MINI_4B.md) -- [PHI\_4\_MINI\_4B\_QUANTIZED](variables/PHI_4_MINI_4B_QUANTIZED.md) -- [QWEN2\_5\_0\_5B](variables/QWEN2_5_0_5B.md) -- [QWEN2\_5\_0\_5B\_QUANTIZED](variables/QWEN2_5_0_5B_QUANTIZED.md) -- [QWEN2\_5\_1\_5B](variables/QWEN2_5_1_5B.md) -- [QWEN2\_5\_1\_5B\_QUANTIZED](variables/QWEN2_5_1_5B_QUANTIZED.md) -- [QWEN2\_5\_3B](variables/QWEN2_5_3B.md) -- [QWEN2\_5\_3B\_QUANTIZED](variables/QWEN2_5_3B_QUANTIZED.md) -- [QWEN3\_0\_6B](variables/QWEN3_0_6B.md) -- [QWEN3\_0\_6B\_QUANTIZED](variables/QWEN3_0_6B_QUANTIZED.md) -- [QWEN3\_1\_7B](variables/QWEN3_1_7B.md) -- [QWEN3\_1\_7B\_QUANTIZED](variables/QWEN3_1_7B_QUANTIZED.md) -- [QWEN3\_4B](variables/QWEN3_4B.md) -- [QWEN3\_4B\_QUANTIZED](variables/QWEN3_4B_QUANTIZED.md) -- [SMOLLM2\_1\_1\_7B](variables/SMOLLM2_1_1_7B.md) -- [SMOLLM2\_1\_1\_7B\_QUANTIZED](variables/SMOLLM2_1_1_7B_QUANTIZED.md) -- [SMOLLM2\_1\_135M](variables/SMOLLM2_1_135M.md) -- [SMOLLM2\_1\_135M\_QUANTIZED](variables/SMOLLM2_1_135M_QUANTIZED.md) -- [SMOLLM2\_1\_360M](variables/SMOLLM2_1_360M.md) -- [SMOLLM2\_1\_360M\_QUANTIZED](variables/SMOLLM2_1_360M_QUANTIZED.md) +- [HAMMER2_1_0_5B](variables/HAMMER2_1_0_5B.md) +- [HAMMER2_1_0_5B_QUANTIZED](variables/HAMMER2_1_0_5B_QUANTIZED.md) +- [HAMMER2_1_1_5B](variables/HAMMER2_1_1_5B.md) +- [HAMMER2_1_1_5B_QUANTIZED](variables/HAMMER2_1_1_5B_QUANTIZED.md) +- [HAMMER2_1_3B](variables/HAMMER2_1_3B.md) +- [HAMMER2_1_3B_QUANTIZED](variables/HAMMER2_1_3B_QUANTIZED.md) +- [LLAMA3_2_1B](variables/LLAMA3_2_1B.md) +- [LLAMA3_2_1B_QLORA](variables/LLAMA3_2_1B_QLORA.md) +- [LLAMA3_2_1B_SPINQUANT](variables/LLAMA3_2_1B_SPINQUANT.md) +- [LLAMA3_2_3B](variables/LLAMA3_2_3B.md) +- [LLAMA3_2_3B_QLORA](variables/LLAMA3_2_3B_QLORA.md) +- [LLAMA3_2_3B_SPINQUANT](variables/LLAMA3_2_3B_SPINQUANT.md) +- [PHI_4_MINI_4B](variables/PHI_4_MINI_4B.md) +- [PHI_4_MINI_4B_QUANTIZED](variables/PHI_4_MINI_4B_QUANTIZED.md) +- [QWEN2_5_0_5B](variables/QWEN2_5_0_5B.md) +- [QWEN2_5_0_5B_QUANTIZED](variables/QWEN2_5_0_5B_QUANTIZED.md) +- [QWEN2_5_1_5B](variables/QWEN2_5_1_5B.md) +- [QWEN2_5_1_5B_QUANTIZED](variables/QWEN2_5_1_5B_QUANTIZED.md) +- [QWEN2_5_3B](variables/QWEN2_5_3B.md) +- [QWEN2_5_3B_QUANTIZED](variables/QWEN2_5_3B_QUANTIZED.md) +- [QWEN3_0_6B](variables/QWEN3_0_6B.md) +- [QWEN3_0_6B_QUANTIZED](variables/QWEN3_0_6B_QUANTIZED.md) +- [QWEN3_1_7B](variables/QWEN3_1_7B.md) +- [QWEN3_1_7B_QUANTIZED](variables/QWEN3_1_7B_QUANTIZED.md) +- [QWEN3_4B](variables/QWEN3_4B.md) +- [QWEN3_4B_QUANTIZED](variables/QWEN3_4B_QUANTIZED.md) +- [SMOLLM2_1_1_7B](variables/SMOLLM2_1_1_7B.md) +- [SMOLLM2_1_1_7B_QUANTIZED](variables/SMOLLM2_1_1_7B_QUANTIZED.md) +- [SMOLLM2_1_135M](variables/SMOLLM2_1_135M.md) +- [SMOLLM2_1_135M_QUANTIZED](variables/SMOLLM2_1_135M_QUANTIZED.md) +- [SMOLLM2_1_360M](variables/SMOLLM2_1_360M.md) +- [SMOLLM2_1_360M_QUANTIZED](variables/SMOLLM2_1_360M_QUANTIZED.md) ## Models - Object Detection -- [SSDLITE\_320\_MOBILENET\_V3\_LARGE](variables/SSDLITE_320_MOBILENET_V3_LARGE.md) +- [SSDLITE_320_MOBILENET_V3_LARGE](variables/SSDLITE_320_MOBILENET_V3_LARGE.md) ## Models - Speech To Text -- [WHISPER\_BASE](variables/WHISPER_BASE.md) -- [WHISPER\_BASE\_EN](variables/WHISPER_BASE_EN.md) -- [WHISPER\_SMALL](variables/WHISPER_SMALL.md) -- [WHISPER\_SMALL\_EN](variables/WHISPER_SMALL_EN.md) -- [WHISPER\_TINY](variables/WHISPER_TINY.md) -- [WHISPER\_TINY\_EN](variables/WHISPER_TINY_EN.md) -- [WHISPER\_TINY\_EN\_QUANTIZED](variables/WHISPER_TINY_EN_QUANTIZED.md) +- [WHISPER_BASE](variables/WHISPER_BASE.md) +- [WHISPER_BASE_EN](variables/WHISPER_BASE_EN.md) +- [WHISPER_SMALL](variables/WHISPER_SMALL.md) +- [WHISPER_SMALL_EN](variables/WHISPER_SMALL_EN.md) +- [WHISPER_TINY](variables/WHISPER_TINY.md) +- [WHISPER_TINY_EN](variables/WHISPER_TINY_EN.md) +- [WHISPER_TINY_EN_QUANTIZED](variables/WHISPER_TINY_EN_QUANTIZED.md) ## Models - Style Transfer -- [STYLE\_TRANSFER\_CANDY](variables/STYLE_TRANSFER_CANDY.md) -- [STYLE\_TRANSFER\_MOSAIC](variables/STYLE_TRANSFER_MOSAIC.md) -- [STYLE\_TRANSFER\_RAIN\_PRINCESS](variables/STYLE_TRANSFER_RAIN_PRINCESS.md) -- [STYLE\_TRANSFER\_UDNIE](variables/STYLE_TRANSFER_UDNIE.md) +- [STYLE_TRANSFER_CANDY](variables/STYLE_TRANSFER_CANDY.md) +- [STYLE_TRANSFER_MOSAIC](variables/STYLE_TRANSFER_MOSAIC.md) +- [STYLE_TRANSFER_RAIN_PRINCESS](variables/STYLE_TRANSFER_RAIN_PRINCESS.md) +- [STYLE_TRANSFER_UDNIE](variables/STYLE_TRANSFER_UDNIE.md) ## Models - Text Embeddings -- [ALL\_MINILM\_L6\_V2](variables/ALL_MINILM_L6_V2.md) -- [ALL\_MPNET\_BASE\_V2](variables/ALL_MPNET_BASE_V2.md) -- [CLIP\_VIT\_BASE\_PATCH32\_TEXT](variables/CLIP_VIT_BASE_PATCH32_TEXT.md) -- [MULTI\_QA\_MINILM\_L6\_COS\_V1](variables/MULTI_QA_MINILM_L6_COS_V1.md) -- [MULTI\_QA\_MPNET\_BASE\_DOT\_V1](variables/MULTI_QA_MPNET_BASE_DOT_V1.md) +- [ALL_MINILM_L6_V2](variables/ALL_MINILM_L6_V2.md) +- [ALL_MPNET_BASE_V2](variables/ALL_MPNET_BASE_V2.md) +- [CLIP_VIT_BASE_PATCH32_TEXT](variables/CLIP_VIT_BASE_PATCH32_TEXT.md) +- [MULTI_QA_MINILM_L6_COS_V1](variables/MULTI_QA_MINILM_L6_COS_V1.md) +- [MULTI_QA_MPNET_BASE_DOT_V1](variables/MULTI_QA_MPNET_BASE_DOT_V1.md) ## Models - Text to Speech -- [KOKORO\_MEDIUM](variables/KOKORO_MEDIUM.md) -- [KOKORO\_SMALL](variables/KOKORO_SMALL.md) +- [KOKORO_MEDIUM](variables/KOKORO_MEDIUM.md) +- [KOKORO_SMALL](variables/KOKORO_SMALL.md) ## Models - Voice Activity Detection -- [FSMN\_VAD](variables/FSMN_VAD.md) +- [FSMN_VAD](variables/FSMN_VAD.md) ## OCR Supported Alphabets -- [OCR\_ABAZA](variables/OCR_ABAZA.md) -- [OCR\_ADYGHE](variables/OCR_ADYGHE.md) -- [OCR\_AFRIKAANS](variables/OCR_AFRIKAANS.md) -- [OCR\_ALBANIAN](variables/OCR_ALBANIAN.md) -- [OCR\_AVAR](variables/OCR_AVAR.md) -- [OCR\_AZERBAIJANI](variables/OCR_AZERBAIJANI.md) -- [OCR\_BELARUSIAN](variables/OCR_BELARUSIAN.md) -- [OCR\_BOSNIAN](variables/OCR_BOSNIAN.md) -- [OCR\_BULGARIAN](variables/OCR_BULGARIAN.md) -- [OCR\_CHECHEN](variables/OCR_CHECHEN.md) -- [OCR\_CROATIAN](variables/OCR_CROATIAN.md) -- [OCR\_CZECH](variables/OCR_CZECH.md) -- [OCR\_DANISH](variables/OCR_DANISH.md) -- [OCR\_DARGWA](variables/OCR_DARGWA.md) -- [OCR\_DUTCH](variables/OCR_DUTCH.md) -- [OCR\_ENGLISH](variables/OCR_ENGLISH.md) -- [OCR\_ESTONIAN](variables/OCR_ESTONIAN.md) -- [OCR\_FRENCH](variables/OCR_FRENCH.md) -- [OCR\_GERMAN](variables/OCR_GERMAN.md) -- [OCR\_HUNGARIAN](variables/OCR_HUNGARIAN.md) -- [OCR\_ICELANDIC](variables/OCR_ICELANDIC.md) -- [OCR\_INDONESIAN](variables/OCR_INDONESIAN.md) -- [OCR\_INGUSH](variables/OCR_INGUSH.md) -- [OCR\_IRISH](variables/OCR_IRISH.md) -- [OCR\_ITALIAN](variables/OCR_ITALIAN.md) -- [OCR\_JAPANESE](variables/OCR_JAPANESE.md) -- [OCR\_KANNADA](variables/OCR_KANNADA.md) -- [OCR\_KARBADIAN](variables/OCR_KARBADIAN.md) -- [OCR\_KOREAN](variables/OCR_KOREAN.md) -- [OCR\_KURDISH](variables/OCR_KURDISH.md) -- [OCR\_LAK](variables/OCR_LAK.md) -- [OCR\_LATIN](variables/OCR_LATIN.md) -- [OCR\_LATVIAN](variables/OCR_LATVIAN.md) -- [OCR\_LEZGHIAN](variables/OCR_LEZGHIAN.md) -- [OCR\_LITHUANIAN](variables/OCR_LITHUANIAN.md) -- [OCR\_MALAY](variables/OCR_MALAY.md) -- [OCR\_MALTESE](variables/OCR_MALTESE.md) -- [OCR\_MAORI](variables/OCR_MAORI.md) -- [OCR\_MONGOLIAN](variables/OCR_MONGOLIAN.md) -- [OCR\_NORWEGIAN](variables/OCR_NORWEGIAN.md) -- [OCR\_OCCITAN](variables/OCR_OCCITAN.md) -- [OCR\_PALI](variables/OCR_PALI.md) -- [OCR\_POLISH](variables/OCR_POLISH.md) -- [OCR\_PORTUGUESE](variables/OCR_PORTUGUESE.md) -- [OCR\_ROMANIAN](variables/OCR_ROMANIAN.md) -- [OCR\_RUSSIAN](variables/OCR_RUSSIAN.md) -- [OCR\_SERBIAN\_CYRILLIC](variables/OCR_SERBIAN_CYRILLIC.md) -- [OCR\_SERBIAN\_LATIN](variables/OCR_SERBIAN_LATIN.md) -- [OCR\_SIMPLIFIED\_CHINESE](variables/OCR_SIMPLIFIED_CHINESE.md) -- [OCR\_SLOVAK](variables/OCR_SLOVAK.md) -- [OCR\_SLOVENIAN](variables/OCR_SLOVENIAN.md) -- [OCR\_SPANISH](variables/OCR_SPANISH.md) -- [OCR\_SWAHILI](variables/OCR_SWAHILI.md) -- [OCR\_SWEDISH](variables/OCR_SWEDISH.md) -- [OCR\_TABASSARAN](variables/OCR_TABASSARAN.md) -- [OCR\_TAGALOG](variables/OCR_TAGALOG.md) -- [OCR\_TAJIK](variables/OCR_TAJIK.md) -- [OCR\_TELUGU](variables/OCR_TELUGU.md) -- [OCR\_TURKISH](variables/OCR_TURKISH.md) -- [OCR\_UKRAINIAN](variables/OCR_UKRAINIAN.md) -- [OCR\_UZBEK](variables/OCR_UZBEK.md) -- [OCR\_VIETNAMESE](variables/OCR_VIETNAMESE.md) -- [OCR\_WELSH](variables/OCR_WELSH.md) +- [OCR_ABAZA](variables/OCR_ABAZA.md) +- [OCR_ADYGHE](variables/OCR_ADYGHE.md) +- [OCR_AFRIKAANS](variables/OCR_AFRIKAANS.md) +- [OCR_ALBANIAN](variables/OCR_ALBANIAN.md) +- [OCR_AVAR](variables/OCR_AVAR.md) +- [OCR_AZERBAIJANI](variables/OCR_AZERBAIJANI.md) +- [OCR_BELARUSIAN](variables/OCR_BELARUSIAN.md) +- [OCR_BOSNIAN](variables/OCR_BOSNIAN.md) +- [OCR_BULGARIAN](variables/OCR_BULGARIAN.md) +- [OCR_CHECHEN](variables/OCR_CHECHEN.md) +- [OCR_CROATIAN](variables/OCR_CROATIAN.md) +- [OCR_CZECH](variables/OCR_CZECH.md) +- [OCR_DANISH](variables/OCR_DANISH.md) +- [OCR_DARGWA](variables/OCR_DARGWA.md) +- [OCR_DUTCH](variables/OCR_DUTCH.md) +- [OCR_ENGLISH](variables/OCR_ENGLISH.md) +- [OCR_ESTONIAN](variables/OCR_ESTONIAN.md) +- [OCR_FRENCH](variables/OCR_FRENCH.md) +- [OCR_GERMAN](variables/OCR_GERMAN.md) +- [OCR_HUNGARIAN](variables/OCR_HUNGARIAN.md) +- [OCR_ICELANDIC](variables/OCR_ICELANDIC.md) +- [OCR_INDONESIAN](variables/OCR_INDONESIAN.md) +- [OCR_INGUSH](variables/OCR_INGUSH.md) +- [OCR_IRISH](variables/OCR_IRISH.md) +- [OCR_ITALIAN](variables/OCR_ITALIAN.md) +- [OCR_JAPANESE](variables/OCR_JAPANESE.md) +- [OCR_KANNADA](variables/OCR_KANNADA.md) +- [OCR_KARBADIAN](variables/OCR_KARBADIAN.md) +- [OCR_KOREAN](variables/OCR_KOREAN.md) +- [OCR_KURDISH](variables/OCR_KURDISH.md) +- [OCR_LAK](variables/OCR_LAK.md) +- [OCR_LATIN](variables/OCR_LATIN.md) +- [OCR_LATVIAN](variables/OCR_LATVIAN.md) +- [OCR_LEZGHIAN](variables/OCR_LEZGHIAN.md) +- [OCR_LITHUANIAN](variables/OCR_LITHUANIAN.md) +- [OCR_MALAY](variables/OCR_MALAY.md) +- [OCR_MALTESE](variables/OCR_MALTESE.md) +- [OCR_MAORI](variables/OCR_MAORI.md) +- [OCR_MONGOLIAN](variables/OCR_MONGOLIAN.md) +- [OCR_NORWEGIAN](variables/OCR_NORWEGIAN.md) +- [OCR_OCCITAN](variables/OCR_OCCITAN.md) +- [OCR_PALI](variables/OCR_PALI.md) +- [OCR_POLISH](variables/OCR_POLISH.md) +- [OCR_PORTUGUESE](variables/OCR_PORTUGUESE.md) +- [OCR_ROMANIAN](variables/OCR_ROMANIAN.md) +- [OCR_RUSSIAN](variables/OCR_RUSSIAN.md) +- [OCR_SERBIAN_CYRILLIC](variables/OCR_SERBIAN_CYRILLIC.md) +- [OCR_SERBIAN_LATIN](variables/OCR_SERBIAN_LATIN.md) +- [OCR_SIMPLIFIED_CHINESE](variables/OCR_SIMPLIFIED_CHINESE.md) +- [OCR_SLOVAK](variables/OCR_SLOVAK.md) +- [OCR_SLOVENIAN](variables/OCR_SLOVENIAN.md) +- [OCR_SPANISH](variables/OCR_SPANISH.md) +- [OCR_SWAHILI](variables/OCR_SWAHILI.md) +- [OCR_SWEDISH](variables/OCR_SWEDISH.md) +- [OCR_TABASSARAN](variables/OCR_TABASSARAN.md) +- [OCR_TAGALOG](variables/OCR_TAGALOG.md) +- [OCR_TAJIK](variables/OCR_TAJIK.md) +- [OCR_TELUGU](variables/OCR_TELUGU.md) +- [OCR_TURKISH](variables/OCR_TURKISH.md) +- [OCR_UKRAINIAN](variables/OCR_UKRAINIAN.md) +- [OCR_UZBEK](variables/OCR_UZBEK.md) +- [OCR_VIETNAMESE](variables/OCR_VIETNAMESE.md) +- [OCR_WELSH](variables/OCR_WELSH.md) ## Other @@ -181,14 +181,14 @@ ## TTS Supported Voices -- [KOKORO\_VOICE\_AF\_HEART](variables/KOKORO_VOICE_AF_HEART.md) -- [KOKORO\_VOICE\_AF\_RIVER](variables/KOKORO_VOICE_AF_RIVER.md) -- [KOKORO\_VOICE\_AF\_SARAH](variables/KOKORO_VOICE_AF_SARAH.md) -- [KOKORO\_VOICE\_AM\_ADAM](variables/KOKORO_VOICE_AM_ADAM.md) -- [KOKORO\_VOICE\_AM\_MICHAEL](variables/KOKORO_VOICE_AM_MICHAEL.md) -- [KOKORO\_VOICE\_AM\_SANTA](variables/KOKORO_VOICE_AM_SANTA.md) -- [KOKORO\_VOICE\_BF\_EMMA](variables/KOKORO_VOICE_BF_EMMA.md) -- [KOKORO\_VOICE\_BM\_DANIEL](variables/KOKORO_VOICE_BM_DANIEL.md) +- [KOKORO_VOICE_AF_HEART](variables/KOKORO_VOICE_AF_HEART.md) +- [KOKORO_VOICE_AF_RIVER](variables/KOKORO_VOICE_AF_RIVER.md) +- [KOKORO_VOICE_AF_SARAH](variables/KOKORO_VOICE_AF_SARAH.md) +- [KOKORO_VOICE_AM_ADAM](variables/KOKORO_VOICE_AM_ADAM.md) +- [KOKORO_VOICE_AM_MICHAEL](variables/KOKORO_VOICE_AM_MICHAEL.md) +- [KOKORO_VOICE_AM_SANTA](variables/KOKORO_VOICE_AM_SANTA.md) +- [KOKORO_VOICE_BF_EMMA](variables/KOKORO_VOICE_BF_EMMA.md) +- [KOKORO_VOICE_BM_DANIEL](variables/KOKORO_VOICE_BM_DANIEL.md) ## Types @@ -251,7 +251,7 @@ - [SpeechToTextLanguage](type-aliases/SpeechToTextLanguage.md) - [TensorBuffer](type-aliases/TensorBuffer.md) - [TextToSpeechLanguage](type-aliases/TextToSpeechLanguage.md) -- [SPECIAL\_TOKENS](variables/SPECIAL_TOKENS.md) +- [SPECIAL_TOKENS](variables/SPECIAL_TOKENS.md) ## Typescript API @@ -277,11 +277,11 @@ ## Utilities - LLM -- [DEFAULT\_CHAT\_CONFIG](variables/DEFAULT_CHAT_CONFIG.md) -- [DEFAULT\_CONTEXT\_WINDOW\_LENGTH](variables/DEFAULT_CONTEXT_WINDOW_LENGTH.md) -- [DEFAULT\_MESSAGE\_HISTORY](variables/DEFAULT_MESSAGE_HISTORY.md) -- [DEFAULT\_SYSTEM\_PROMPT](variables/DEFAULT_SYSTEM_PROMPT.md) +- [DEFAULT_CHAT_CONFIG](variables/DEFAULT_CHAT_CONFIG.md) +- [DEFAULT_CONTEXT_WINDOW_LENGTH](variables/DEFAULT_CONTEXT_WINDOW_LENGTH.md) +- [DEFAULT_MESSAGE_HISTORY](variables/DEFAULT_MESSAGE_HISTORY.md) +- [DEFAULT_SYSTEM_PROMPT](variables/DEFAULT_SYSTEM_PROMPT.md) - [parseToolCall](variables/parseToolCall.md) -- [DEFAULT\_STRUCTURED\_OUTPUT\_PROMPT](functions/DEFAULT_STRUCTURED_OUTPUT_PROMPT.md) +- [DEFAULT_STRUCTURED_OUTPUT_PROMPT](functions/DEFAULT_STRUCTURED_OUTPUT_PROMPT.md) - [fixAndValidateStructuredOutput](functions/fixAndValidateStructuredOutput.md) - [getStructuredOutputPrompt](functions/getStructuredOutputPrompt.md) diff --git a/docs/src/theme/SearchBar.tsx b/docs/src/theme/SearchBar.tsx index 7536c3716..8dd05bdab 100644 --- a/docs/src/theme/SearchBar.tsx +++ b/docs/src/theme/SearchBar.tsx @@ -2,4 +2,4 @@ import React from 'react'; export default function SearchBar() { return
; -} \ No newline at end of file +} diff --git a/docs/versioned_docs/version-0.6.x/02-hooks/02-computer-vision/useOCR.md b/docs/versioned_docs/version-0.6.x/02-hooks/02-computer-vision/useOCR.md index 0c804e1a0..d491ed65b 100644 --- a/docs/versioned_docs/version-0.6.x/02-hooks/02-computer-vision/useOCR.md +++ b/docs/versioned_docs/version-0.6.x/02-hooks/02-computer-vision/useOCR.md @@ -309,7 +309,7 @@ You need to make sure the recognizer models you pass in `recognizerSources` matc | ![Alt text](../../../../static/img/harvard.png) | ![Alt text](../../../../static/img/harvard-boxes.png) | | ----------------------------------------------- | ----------------------------------------------------- | -| Original Image | Image with detected Text Boxes | +| Original Image | Image with detected Text Boxes | :::warning Times presented in the tables are measured as consecutive runs of the model. Initial run times may be up to 2x longer due to model loading and initialization. diff --git a/docs/versioned_docs/version-0.6.x/02-hooks/02-computer-vision/useTextToImage.md b/docs/versioned_docs/version-0.6.x/02-hooks/02-computer-vision/useTextToImage.md index 1d35d7a3a..476f8d95d 100644 --- a/docs/versioned_docs/version-0.6.x/02-hooks/02-computer-vision/useTextToImage.md +++ b/docs/versioned_docs/version-0.6.x/02-hooks/02-computer-vision/useTextToImage.md @@ -94,7 +94,7 @@ function App() { | ![Castle 256x256](../../../../static/img/castle256.png) | ![Castle 512x512](../../../../static/img/castle512.png) | | ------------------------------------------------------- | ------------------------------------------------------- | -| Image of size 256×256 | Image of size 512×512 | +| Image of size 256×256 | Image of size 512×512 | ## Supported models diff --git a/docs/versioned_docs/version-0.6.x/02-hooks/02-computer-vision/useVerticalOCR.md b/docs/versioned_docs/version-0.6.x/02-hooks/02-computer-vision/useVerticalOCR.md index 33f4d935f..88934fd2e 100644 --- a/docs/versioned_docs/version-0.6.x/02-hooks/02-computer-vision/useVerticalOCR.md +++ b/docs/versioned_docs/version-0.6.x/02-hooks/02-computer-vision/useVerticalOCR.md @@ -324,7 +324,7 @@ You need to make sure the recognizer models you pass in `recognizerSources` matc | ![Alt text](../../../../static/img/sales-vertical.jpeg) | ![Alt text](../../../../static/img/sales-vertical-boxes.png) | | ------------------------------------------------------- | ------------------------------------------------------------ | -| Original Image | Image with detected Text Boxes | +| Original Image | Image with detected Text Boxes | :::warning Times presented in the tables are measured as consecutive runs of the model. Initial run times may be up to 2x longer due to model loading and initialization. diff --git a/package.json b/package.json index e4ee020b2..33cbcc2d9 100644 --- a/package.json +++ b/package.json @@ -4,7 +4,7 @@ "packageManager": "yarn@4.1.1", "workspaces": { "packages": [ - "packages/react-native-executorch", + "packages/*", "apps/*" ] }, diff --git a/packages/bare-resource-fetcher/README.md b/packages/bare-resource-fetcher/README.md new file mode 100644 index 000000000..9b1493f1d --- /dev/null +++ b/packages/bare-resource-fetcher/README.md @@ -0,0 +1,37 @@ +# @react-native-executorch/bare-resource-fetcher + +Bare React Native adapter for `react-native-executorch` that provides resource fetching capabilities using native filesystem libraries. + +## Installation + +```bash +yarn add @react-native-executorch/bare-resource-fetcher +yarn add @dr.pogodin/react-native-fs @kesha-antonov/react-native-background-downloader +``` + +### Native Dependencies Setup + +After installing, follow the setup guides for the native dependencies: + +- **[@dr.pogodin/react-native-fs](https://github.com/birdofpreyru/react-native-fs#getting-started)** - Filesystem operations +- **[@kesha-antonov/react-native-background-downloader](https://github.com/kesha-antonov/react-native-background-downloader#bare-react-native-projects)** - Background download support + +> **Note**: Make sure to complete the native setup (iOS/Android configuration) for both dependencies before using this adapter. + +## Usage + +```typescript +import { initExecutorch } from 'react-native-executorch'; +import { BareResourceFetcher } from '@react-native-executorch/bare-resource-fetcher'; + +initExecutorch({ + resourceFetcher: BareResourceFetcher, +}); +``` + +## When to Use + +Use this adapter if you're working with: +- Bare React Native projects (created with `npx @react-native-community/cli@latest init`) +- Projects that need true background downloads +- Projects requiring direct native filesystem access diff --git a/packages/bare-resource-fetcher/package.json b/packages/bare-resource-fetcher/package.json new file mode 100644 index 000000000..b5533ac48 --- /dev/null +++ b/packages/bare-resource-fetcher/package.json @@ -0,0 +1,43 @@ +{ + "name": "@react-native-executorch/bare-resource-fetcher", + "version": "0.1.0", + "description": "Bare React Native resource fetcher for react-native-executorch", + "main": "lib/index.js", + "types": "lib/index.d.ts", + "exports": { + ".": { + "import": "./lib/index.js", + "types": "./lib/index.d.ts" + } + }, + "files": [ + "lib" + ], + "license": "MIT", + "repository": { + "type": "git", + "url": "git+https://github.com/software-mansion/react-native-executorch.git", + "directory": "packages/bare-resource-fetcher" + }, + "scripts": { + "prepare": "tsc", + "typecheck": "tsc --noEmit", + "lint": "eslint \"**/*.{js,ts,tsx}\"", + "clean": "del-cli lib" + }, + "peerDependencies": { + "@dr.pogodin/react-native-fs": "^2.0.0", + "@kesha-antonov/react-native-background-downloader": "^4.0.0", + "react-native": "*", + "react-native-executorch": "*" + }, + "devDependencies": { + "@dr.pogodin/react-native-fs": "^2.36.2", + "@kesha-antonov/react-native-background-downloader": "^4.4.5", + "@types/react": "~19.1.10", + "react": "19.1.0", + "react-native": "0.81.5", + "react-native-executorch": "workspace:*", + "typescript": "~5.9.2" + } +} diff --git a/packages/bare-resource-fetcher/src/ResourceFetcher.ts b/packages/bare-resource-fetcher/src/ResourceFetcher.ts new file mode 100644 index 000000000..c5cee0fd7 --- /dev/null +++ b/packages/bare-resource-fetcher/src/ResourceFetcher.ts @@ -0,0 +1,572 @@ +/** + * Resource Fetcher for React Native applications. + * + * This module provides functions to download and manage files stored in the application's document directory + * inside the `react-native-executorch/` directory. These utilities help manage storage and clean up downloaded + * files when they are no longer needed. + * + * @category Utilities - General + * + * @remarks + * **Key Functionality:** + * - **Download Control**: Pause, resume, and cancel operations through: + * - {@link pauseFetching} - Pause ongoing downloads + * - {@link resumeFetching} - Resume paused downloads + * - {@link cancelFetching} - Cancel ongoing or paused downloads + * - **File Management**: + * - {@link getFilesTotalSize} - Get total size of resources + * - {@link listDownloadedFiles} - List all downloaded files + * - {@link listDownloadedModels} - List downloaded model files (.pte) + * - {@link deleteResources} - Delete downloaded resources + * + * **Important Notes:** + * - Pause/resume/cancel operations work only for remote resources + * - Most functions accept multiple `ResourceSource` arguments (string, number, or object) + * - The {@link fetch} method accepts a progress callback (0-1) and returns file paths or null if interrupted + * + * **Technical Implementation:** + * - Maintains a `downloads` Map to track active and paused downloads + * - Successful downloads are automatically removed from the Map + * - Uses `ResourceSourceExtended` interface for pause/resume functionality with linked-list behavior + */ + +import { + createDownloadTask, + completeHandler, + DownloadTask, + BeginHandlerParams, + ProgressHandlerParams, +} from '@kesha-antonov/react-native-background-downloader'; +import * as RNFS from '@dr.pogodin/react-native-fs'; +import { Image } from 'react-native'; +import { RNEDirectory } from './constants/directories'; +import { + ResourceSource, + ResourceFetcherAdapter, + RnExecutorchErrorCode, + RnExecutorchError, +} from 'react-native-executorch'; +import { + ResourceFetcherUtils, + DownloadStatus, + SourceType, + ResourceSourceExtended, +} from './ResourceFetcherUtils'; + +interface DownloadResource { + task: DownloadTask; + status: DownloadStatus; + extendedInfo: ResourceSourceExtended; +} + +interface BareResourceFetcherInterface extends ResourceFetcherAdapter { + downloads: Map; + singleFetch(sourceExtended: ResourceSourceExtended): Promise; + returnOrStartNext( + sourceExtended: ResourceSourceExtended, + result: string | string[] + ): string[] | Promise; + completeDownload( + extendedInfo: ResourceSourceExtended, + source: ResourceSource + ): Promise; + pause(source: ResourceSource): Promise; + resume(source: ResourceSource): Promise; + cancel(source: ResourceSource): Promise; + findActive(sources: ResourceSource[]): ResourceSource; + pauseFetching(...sources: ResourceSource[]): Promise; + resumeFetching(...sources: ResourceSource[]): Promise; + cancelFetching(...sources: ResourceSource[]): Promise; + listDownloadedFiles(): Promise; + listDownloadedModels(): Promise; + deleteResources(...sources: ResourceSource[]): Promise; + getFilesTotalSize(...sources: ResourceSource[]): Promise; + handleObject(source: ResourceSource): Promise; + handleLocalFile(source: ResourceSource): string; + handleReleaseModeFile( + sourceExtended: ResourceSourceExtended + ): Promise; + handleDevModeFile( + sourceExtended: ResourceSourceExtended + ): Promise; + handleRemoteFile( + sourceExtended: ResourceSourceExtended + ): Promise; +} + +/** + * This module provides functions to download and work with downloaded files stored in the application's document directory inside the `react-native-executorch/` directory. + * These utilities can help you manage your storage and clean up the downloaded files when they are no longer needed. + * + * @category Utilities - General + */ +export const BareResourceFetcher: BareResourceFetcherInterface = { + downloads: new Map(), //map of currently downloading (or paused) files, if the download was started by .fetch() method. + + /** + * Fetches resources (remote URLs, local files or embedded assets), downloads or stores them locally for use by React Native ExecuTorch. + * + * @param callback - Optional callback to track progress of all downloads, reported between 0 and 1. + * @param sources - Multiple resources that can be strings, asset references, or objects. + * @returns If the fetch was successful, it returns a promise which resolves to an array of local file paths for the downloaded/stored resources (without file:// prefix). + * If the fetch was interrupted by `pauseFetching` or `cancelFetching`, it returns a promise which resolves to `null`. + */ + async fetch( + callback: (downloadProgress: number) => void = () => {}, + ...sources: ResourceSource[] + ) { + if (sources.length === 0) { + throw new RnExecutorchError( + RnExecutorchErrorCode.InvalidUserInput, + 'Empty list given as an argument' + ); + } + const { results: info, totalLength } = + await ResourceFetcherUtils.getFilesSizes(sources); + const head: ResourceSourceExtended = { + source: info[0]!.source, + sourceType: info[0]!.type, + callback: + info[0]!.type === SourceType.REMOTE_FILE + ? ResourceFetcherUtils.calculateDownloadProgress( + totalLength, + info[0]!.previousFilesTotalLength, + info[0]!.length, + callback + ) + : () => {}, + results: [], + }; + + let node = head; + for (let idx = 1; idx < sources.length; idx++) { + node.next = { + source: info[idx]!.source, + sourceType: info[idx]!.type, + callback: + info[idx]!.type === SourceType.REMOTE_FILE + ? ResourceFetcherUtils.calculateDownloadProgress( + totalLength, + info[idx]!.previousFilesTotalLength, + info[idx]!.length, + callback + ) + : () => {}, + results: [], + }; + node = node.next; + } + return this.singleFetch(head); + }, + + async singleFetch( + sourceExtended: ResourceSourceExtended + ): Promise { + const source = sourceExtended.source; + switch (sourceExtended.sourceType) { + case SourceType.OBJECT: { + return this.returnOrStartNext( + sourceExtended, + await this.handleObject(source) + ); + } + case SourceType.LOCAL_FILE: { + return this.returnOrStartNext( + sourceExtended, + this.handleLocalFile(source) + ); + } + case SourceType.RELEASE_MODE_FILE: { + return this.returnOrStartNext( + sourceExtended, + await this.handleReleaseModeFile(sourceExtended) + ); + } + case SourceType.DEV_MODE_FILE: { + const result = await this.handleDevModeFile(sourceExtended); + if (result !== null) { + return this.returnOrStartNext(sourceExtended, result); + } + return null; + } + default: { + //case SourceType.REMOTE_FILE + const result = await this.handleRemoteFile(sourceExtended); + if (result !== null) { + return this.returnOrStartNext(sourceExtended, result); + } + return null; + } + } + }, + + //if any download ends successfully this function is called - it checks whether it should trigger next download or return list of paths. + returnOrStartNext(sourceExtended: ResourceSourceExtended, result: string) { + sourceExtended.results.push(result); + + if (sourceExtended.next) { + const nextSource = sourceExtended.next; + nextSource.results.push(...sourceExtended.results); + return this.singleFetch(nextSource); + } + sourceExtended.callback!(1); + return sourceExtended.results; + }, + + async pause(source: ResourceSource) { + const resource = this.downloads.get(source); + if (!resource) { + throw new RnExecutorchError( + RnExecutorchErrorCode.NotFound, + 'No active download found for the given source' + ); + } + switch (resource.status) { + case DownloadStatus.PAUSED: + throw new RnExecutorchError( + RnExecutorchErrorCode.ResourceFetcherAlreadyPaused, + "The file download is currently paused. Can't pause the download of the same file twice." + ); + default: { + resource.status = DownloadStatus.PAUSED; + resource.task.pause(); + } + } + }, + + async resume(source: ResourceSource) { + const resource = this.downloads.get(source)!; + if ( + !resource.extendedInfo.fileUri || + !resource.extendedInfo.cacheFileUri || + !resource.extendedInfo.uri + ) { + throw new RnExecutorchError( + RnExecutorchErrorCode.ResourceFetcherMissingUri, + 'Something went wrong. File uri info is not specified' + ); + } + switch (resource.status) { + case DownloadStatus.ONGOING: + throw new RnExecutorchError( + RnExecutorchErrorCode.ResourceFetcherAlreadyOngoing, + "The file download is currently ongoing. Can't resume the ongoing download." + ); + default: { + resource.status = DownloadStatus.ONGOING; + resource.task.resume(); + + return new Promise((resolve, reject) => { + resource.task + .done(async () => { + const result = await this.completeDownload( + resource.extendedInfo, + source + ); + resolve(result); + }) + .error((e) => { + reject(e); + }); + }); + } + } + }, + + async cancel(source: ResourceSource) { + const resource = this.downloads.get(source); + if (!resource) { + throw new RnExecutorchError( + RnExecutorchErrorCode.NotFound, + 'No active download found for the given source' + ); + } + resource.task.stop(); + this.downloads.delete(source); + }, + + async completeDownload( + extendedInfo: ResourceSourceExtended, + source: ResourceSource + ): Promise { + // Check if download was cancelled or paused + if ( + !this.downloads.has(source) || + this.downloads.get(source)!.status === DownloadStatus.PAUSED + ) { + return null; + } + + await RNFS.moveFile(extendedInfo.cacheFileUri!, extendedInfo.fileUri!); + this.downloads.delete(source); + ResourceFetcherUtils.triggerHuggingFaceDownloadCounter(extendedInfo.uri!); + + const filename = extendedInfo.fileUri!.split('/').pop(); + if (filename) { + await completeHandler(filename); + } + + const result = this.returnOrStartNext( + extendedInfo, + ResourceFetcherUtils.removeFilePrefix(extendedInfo.fileUri!) + ); + return result instanceof Promise ? await result : result; + }, + + /** + * Pauses an ongoing download of files. + * + * @param sources - The resource identifiers used when calling `fetch`. + * @returns A promise that resolves once the download is paused. + */ + async pauseFetching(...sources: ResourceSource[]) { + const source = this.findActive(sources); + await this.pause(source); + }, + + /** + * Resumes a paused download of files. + * + * @param sources - The resource identifiers used when calling fetch. + * @returns If the fetch was successful, it returns a promise which resolves to an array of local file paths for the downloaded resources (without file:// prefix). + * If the fetch was again interrupted by `pauseFetching` or `cancelFetching`, it returns a promise which resolves to `null`. + */ + async resumeFetching(...sources: ResourceSource[]) { + const source = this.findActive(sources); + await this.resume(source); + }, + + /** + * Cancels an ongoing/paused download of files. + * + * @param sources - The resource identifiers used when calling `fetch()`. + * @returns A promise that resolves once the download is canceled. + */ + async cancelFetching(...sources: ResourceSource[]) { + const source = this.findActive(sources); + await this.cancel(source); + }, + + findActive(sources: ResourceSource[]) { + for (const source of sources) { + if (this.downloads.has(source)) { + return source; + } + } + throw new RnExecutorchError( + RnExecutorchErrorCode.ResourceFetcherNotActive, + 'None of given sources are currently during downloading process.' + ); + }, + + /** + * Lists all the downloaded files used by React Native ExecuTorch. + * + * @returns A promise, which resolves to an array of URIs for all the downloaded files. + */ + async listDownloadedFiles() { + const files = await RNFS.readDir(RNEDirectory); + return files.map((file) => file.path); + }, + + /** + * Lists all the downloaded models used by React Native ExecuTorch. + * + * @returns A promise, which resolves to an array of URIs for all the downloaded models. + */ + async listDownloadedModels() { + const files = await this.listDownloadedFiles(); + return files.filter((file: string) => file.endsWith('.pte')); + }, + + /** + * Deletes downloaded resources from the local filesystem. + * + * @param sources - The resource identifiers used when calling `fetch`. + * @returns A promise that resolves once all specified resources have been removed. + */ + async deleteResources(...sources: ResourceSource[]) { + for (const source of sources) { + const filename = ResourceFetcherUtils.getFilenameFromUri( + source as string + ); + const fileUri = `${RNEDirectory}${filename}`; + if (await ResourceFetcherUtils.checkFileExists(fileUri)) { + await RNFS.unlink(fileUri); + } + } + }, + + /** + * Fetches the info about files size. Works only for remote files. + * + * @param sources - The resource identifiers (URLs). + * @returns A promise that resolves to combined size of files in bytes. + */ + async getFilesTotalSize(...sources: ResourceSource[]) { + return (await ResourceFetcherUtils.getFilesSizes(sources)).totalLength; + }, + + async handleObject(source: ResourceSource) { + if (typeof source !== 'object') { + throw new RnExecutorchError( + RnExecutorchErrorCode.InvalidModelSource, + 'Source is expected to be object' + ); + } + const jsonString = JSON.stringify(source); + const digest = ResourceFetcherUtils.hashObject(jsonString); + const filename = `${digest}.json`; + const path = `${RNEDirectory}${filename}`; + + if (await ResourceFetcherUtils.checkFileExists(path)) { + return ResourceFetcherUtils.removeFilePrefix(path); + } + + await ResourceFetcherUtils.createDirectoryIfNoExists(); + await RNFS.writeFile(path, jsonString, 'utf8'); + + return ResourceFetcherUtils.removeFilePrefix(path); + }, + + handleLocalFile(source: ResourceSource) { + if (typeof source !== 'string') { + throw new RnExecutorchError( + RnExecutorchErrorCode.InvalidModelSource, + 'Source is expected to be string' + ); + } + return ResourceFetcherUtils.removeFilePrefix(source); + }, + + async handleReleaseModeFile(sourceExtended: ResourceSourceExtended) { + const source = sourceExtended.source; + if (typeof source !== 'number') { + throw new RnExecutorchError( + RnExecutorchErrorCode.InvalidModelSource, + 'Source is expected to be number' + ); + } + const assetSource = Image.resolveAssetSource(source); + const uri = assetSource.uri; + const filename = ResourceFetcherUtils.getFilenameFromUri(uri); + const fileUri = `${RNEDirectory}${filename}`; + + if (await ResourceFetcherUtils.checkFileExists(fileUri)) { + return ResourceFetcherUtils.removeFilePrefix(fileUri); + } + await ResourceFetcherUtils.createDirectoryIfNoExists(); + + if (uri.startsWith('http') || uri.startsWith('file')) { + await RNFS.copyFile(uri, fileUri); + } + return ResourceFetcherUtils.removeFilePrefix(fileUri); + }, + + async handleDevModeFile(sourceExtended: ResourceSourceExtended) { + const source = sourceExtended.source; + if (typeof source !== 'number') { + throw new RnExecutorchError( + RnExecutorchErrorCode.InvalidModelSource, + 'Source is expected to be a number' + ); + } + sourceExtended.uri = Image.resolveAssetSource(source).uri; + return await this.handleRemoteFile(sourceExtended); + }, + + async handleRemoteFile(sourceExtended: ResourceSourceExtended) { + const source = sourceExtended.source; + if (typeof source === 'object') { + throw new RnExecutorchError( + RnExecutorchErrorCode.InvalidModelSource, + 'Source is expected to be a string or a number' + ); + } + if (this.downloads.has(source)) { + const resource = this.downloads.get(source)!; + if (resource.status === DownloadStatus.PAUSED) { + // if the download is paused, `fetch` is treated like `resume` + return this.resume(source); + } + // if the download is ongoing, throw error. + throw new RnExecutorchError( + RnExecutorchErrorCode.ResourceFetcherDownloadInProgress, + 'Already downloading this file' + ); + } + if (typeof source === 'number' && !sourceExtended.uri) { + throw new RnExecutorchError( + RnExecutorchErrorCode.ResourceFetcherMissingUri, + 'Source Uri is expected to be available here' + ); + } + if (typeof source === 'string') { + sourceExtended.uri = source; + } + const uri = sourceExtended.uri!; + const filename = ResourceFetcherUtils.getFilenameFromUri(uri); + sourceExtended.fileUri = `${RNEDirectory}${filename}`; + sourceExtended.cacheFileUri = `${RNFS.CachesDirectoryPath}/${filename}`; + + if (await ResourceFetcherUtils.checkFileExists(sourceExtended.fileUri)) { + return ResourceFetcherUtils.removeFilePrefix(sourceExtended.fileUri); + } + await ResourceFetcherUtils.createDirectoryIfNoExists(); + + return new Promise((resolve, reject) => { + const task = createDownloadTask({ + id: filename, + url: uri, + destination: sourceExtended.cacheFileUri!, + }) + .begin((_: BeginHandlerParams) => { + sourceExtended.callback!(0); + }) + .progress((progress: ProgressHandlerParams) => { + sourceExtended.callback!( + progress.bytesDownloaded / progress.bytesTotal + ); + }) + .done(async () => { + const nextResult = await this.completeDownload( + sourceExtended, + source + ); + resolve(nextResult); + }) + .error((error) => { + this.downloads.delete(source); + reject( + new RnExecutorchError( + RnExecutorchErrorCode.ResourceFetcherDownloadFailed, + `Failed to fetch resource from '${source}', context: ${error}` + ) + ); + }); + + // Start the download task + task.start(); + + const downloadResource: DownloadResource = { + task: task, + status: DownloadStatus.ONGOING, + extendedInfo: sourceExtended, + }; + this.downloads.set(source, downloadResource); + }); + }, + + /** + * Reads the contents of a file as a string. + * + * @param path - Absolute file path to read. + * @returns A promise that resolves to the file contents as a string. + * + * @remarks + * **REQUIRED**: Used internally for reading configuration files (e.g., tokenizer configs). + */ + async readAsString(path: string) { + return await RNFS.readFile(path, 'utf8'); + }, +}; diff --git a/packages/bare-resource-fetcher/src/ResourceFetcherUtils.ts b/packages/bare-resource-fetcher/src/ResourceFetcherUtils.ts new file mode 100644 index 000000000..0f65ab3d9 --- /dev/null +++ b/packages/bare-resource-fetcher/src/ResourceFetcherUtils.ts @@ -0,0 +1,102 @@ +import { RNEDirectory } from './constants/directories'; +import { + ResourceSource, + Logger, + ResourceFetcherUtils as CoreUtils, + HTTP_CODE, + DownloadStatus, + SourceType, + ResourceSourceExtended, + RnExecutorchError, + RnExecutorchErrorCode, +} from 'react-native-executorch'; +import { Image } from 'react-native'; +import * as RNFS from '@dr.pogodin/react-native-fs'; + +export { HTTP_CODE, DownloadStatus, SourceType }; +export type { ResourceSourceExtended }; + +export namespace ResourceFetcherUtils { + export const removeFilePrefix = CoreUtils.removeFilePrefix; + export const hashObject = CoreUtils.hashObject; + export const calculateDownloadProgress = CoreUtils.calculateDownloadProgress; + export const triggerHuggingFaceDownloadCounter = + CoreUtils.triggerHuggingFaceDownloadCounter; + export const getFilenameFromUri = CoreUtils.getFilenameFromUri; + + export function getType(source: ResourceSource): SourceType { + if (typeof source === 'object') { + return SourceType.OBJECT; + } else if (typeof source === 'number') { + const uri = Image.resolveAssetSource(source).uri; + if (uri.startsWith('http')) { + return SourceType.DEV_MODE_FILE; + } + return SourceType.RELEASE_MODE_FILE; + } + // typeof source == 'string' + if (source.startsWith('file://')) { + return SourceType.LOCAL_FILE; + } + return SourceType.REMOTE_FILE; + } + + export async function getFilesSizes(sources: ResourceSource[]) { + const results: Array<{ + source: ResourceSource; + type: SourceType; + length: number; + previousFilesTotalLength: number; + }> = []; + let totalLength = 0; + let previousFilesTotalLength = 0; + for (const source of sources) { + const type = ResourceFetcherUtils.getType(source); + let length = 0; + try { + if (type === SourceType.REMOTE_FILE && typeof source === 'string') { + const response = await fetch(source, { method: 'HEAD' }); + if (!response.ok) { + Logger.warn( + `Failed to fetch HEAD for ${source}: ${response.status}` + ); + continue; + } + + const contentLength = response.headers.get('content-length'); + if (!contentLength) { + Logger.warn(`No content-length header for ${source}`); + } + + length = contentLength ? parseInt(contentLength, 10) : 0; + previousFilesTotalLength = totalLength; + totalLength += length; + } + } catch (error) { + Logger.warn(`Error fetching HEAD for ${source}:`, error); + continue; + } finally { + results.push({ source, type, length, previousFilesTotalLength }); + } + } + return { results, totalLength }; + } + + export async function createDirectoryIfNoExists() { + if (!(await checkFileExists(RNEDirectory))) { + try { + await RNFS.mkdir(RNEDirectory); + } catch (error) { + throw new RnExecutorchError( + RnExecutorchErrorCode.FileWriteFailed, + `Failed to create directory at ${RNEDirectory}`, + error + ); + } + } + } + + export async function checkFileExists(fileUri: string) { + return await RNFS.exists(fileUri); + } +} diff --git a/packages/bare-resource-fetcher/src/constants/directories.ts b/packages/bare-resource-fetcher/src/constants/directories.ts new file mode 100644 index 000000000..4ba8b8401 --- /dev/null +++ b/packages/bare-resource-fetcher/src/constants/directories.ts @@ -0,0 +1,3 @@ +import { directories } from '@kesha-antonov/react-native-background-downloader'; + +export const RNEDirectory = `${directories.documents}/react-native-executorch/`; diff --git a/packages/bare-resource-fetcher/src/index.ts b/packages/bare-resource-fetcher/src/index.ts new file mode 100644 index 000000000..c0ec4024f --- /dev/null +++ b/packages/bare-resource-fetcher/src/index.ts @@ -0,0 +1 @@ +export * from './ResourceFetcher'; diff --git a/packages/bare-resource-fetcher/tsconfig.json b/packages/bare-resource-fetcher/tsconfig.json new file mode 100644 index 000000000..cadd2509a --- /dev/null +++ b/packages/bare-resource-fetcher/tsconfig.json @@ -0,0 +1,28 @@ +{ + "extends": "../../tsconfig.json", + "compilerOptions": { + "rootDir": "src", + "outDir": "lib", + "declaration": true, + "declarationMap": true, + "tsBuildInfoFile": "./lib/typescript/tsconfig.tsbuildinfo", + "composite": true, + "allowJs": false, + "allowUnreachableCode": false, + "allowUnusedLabels": false, + "esModuleInterop": true, + "forceConsistentCasingInFileNames": true, + "module": "esnext", + "moduleResolution": "node", + "noFallthroughCasesInSwitch": true, + "noImplicitReturns": true, + "noStrictGenericChecks": false, + "noUnusedLocals": true, + "noUnusedParameters": true, + "noUncheckedIndexedAccess": true, + "strict": true, + "types": ["react", "node"] + }, + "include": ["src"], + "exclude": ["node_modules", "lib"] +} diff --git a/packages/expo-resource-fetcher/README.md b/packages/expo-resource-fetcher/README.md new file mode 100644 index 000000000..279853434 --- /dev/null +++ b/packages/expo-resource-fetcher/README.md @@ -0,0 +1,30 @@ +# @react-native-executorch/expo-resource-fetcher + +Expo adapter for `react-native-executorch` that provides resource fetching capabilities using Expo's filesystem APIs. + +## Installation + +```bash +yarn add @react-native-executorch/expo-resource-fetcher +yarn add expo-file-system expo-asset +``` + +## Usage + +```typescript +import { initExecutorch } from 'react-native-executorch'; +import { ExpoResourceFetcher } from '@react-native-executorch/expo-resource-fetcher'; + +initExecutorch({ + resourceFetcher: ExpoResourceFetcher, +}); +``` + +## When to Use + +Use this adapter if you're working with: +- Expo projects +- Expo Router projects +- Projects using Expo managed workflow + +This adapter leverages `expo-file-system` and `expo-asset` to handle file operations and downloads. diff --git a/packages/expo-resource-fetcher/package.json b/packages/expo-resource-fetcher/package.json new file mode 100644 index 000000000..7cf89487f --- /dev/null +++ b/packages/expo-resource-fetcher/package.json @@ -0,0 +1,45 @@ +{ + "name": "@react-native-executorch/expo-resource-fetcher", + "version": "0.1.0", + "description": "Expo resource fetcher for react-native-executorch", + "main": "lib/index.js", + "types": "lib/index.d.ts", + "exports": { + ".": { + "import": "./lib/index.js", + "types": "./lib/index.d.ts" + } + }, + "files": [ + "lib" + ], + "license": "MIT", + "repository": { + "type": "git", + "url": "git+https://github.com/software-mansion/react-native-executorch.git", + "directory": "packages/expo-resource-fetcher" + }, + "scripts": { + "prepare": "tsc", + "typecheck": "tsc --noEmit", + "lint": "eslint \"**/*.{js,ts,tsx}\"", + "clean": "del-cli lib" + }, + "peerDependencies": { + "expo": ">=54.0.0", + "expo-asset": "^12.0.0", + "expo-file-system": "^19.0.0", + "react-native": "*", + "react-native-executorch": "*" + }, + "devDependencies": { + "@types/react": "~19.1.10", + "expo": "^54.0.0", + "expo-asset": "12.0.11", + "expo-file-system": "^19.0.20", + "react": "19.1.0", + "react-native": "0.81.5", + "react-native-executorch": "workspace:*", + "typescript": "~5.9.2" + } +} diff --git a/packages/expo-resource-fetcher/src/ResourceFetcher.ts b/packages/expo-resource-fetcher/src/ResourceFetcher.ts new file mode 100644 index 000000000..4a81bdfe6 --- /dev/null +++ b/packages/expo-resource-fetcher/src/ResourceFetcher.ts @@ -0,0 +1,561 @@ +/** + * Resource Fetcher for Expo applications. + * + * This module provides functions to download and manage files stored in the application's document directory + * inside the `react-native-executorch/` directory. These utilities help manage storage and clean up downloaded + * files when they are no longer needed. + * + * @category Utilities - General + * + * @remarks + * **Key Functionality:** + * - **Download Control**: Pause, resume, and cancel operations through: + * - {@link pauseFetching} - Pause ongoing downloads + * - {@link resumeFetching} - Resume paused downloads + * - {@link cancelFetching} - Cancel ongoing or paused downloads + * - **File Management**: + * - {@link getFilesTotalSize} - Get total size of resources + * - {@link listDownloadedFiles} - List all downloaded files + * - {@link listDownloadedModels} - List downloaded model files (.pte) + * - {@link deleteResources} - Delete downloaded resources + * + * **Important Notes:** + * - Pause/resume/cancel operations work only for remote resources + * - Most functions accept multiple `ResourceSource` arguments (string, number, or object) + * - The {@link fetch} method accepts a progress callback (0-1) and returns file paths or null if interrupted + * + * **Technical Implementation:** + * - Maintains a `downloads` Map to track active and paused downloads + * - Successful downloads are automatically removed from the Map + * - Uses `ResourceSourceExtended` interface for pause/resume functionality with linked-list behavior + */ + +import { + cacheDirectory, + copyAsync, + createDownloadResumable, + moveAsync, + FileSystemSessionType, + writeAsStringAsync, + EncodingType, + deleteAsync, + readDirectoryAsync, + readAsStringAsync, +} from 'expo-file-system/legacy'; +import { Asset } from 'expo-asset'; +import { Platform } from 'react-native'; +import { RNEDirectory } from './constants/directories'; +import { + ResourceSource, + ResourceFetcherAdapter, + RnExecutorchErrorCode, + RnExecutorchError, +} from 'react-native-executorch'; +import { + ResourceFetcherUtils, + HTTP_CODE, + DownloadStatus, + SourceType, + ResourceSourceExtended, + DownloadResource, +} from './ResourceFetcherUtils'; + +interface ExpoResourceFetcherInterface extends ResourceFetcherAdapter { + downloads: Map; + singleFetch(sourceExtended: ResourceSourceExtended): Promise; + returnOrStartNext( + sourceExtended: ResourceSourceExtended, + result: string | string[] + ): string[] | Promise; + pause(source: ResourceSource): Promise; + resume(source: ResourceSource): Promise; + cancel(source: ResourceSource): Promise; + findActive(sources: ResourceSource[]): ResourceSource; + pauseFetching(...sources: ResourceSource[]): Promise; + resumeFetching(...sources: ResourceSource[]): Promise; + cancelFetching(...sources: ResourceSource[]): Promise; + listDownloadedFiles(): Promise; + listDownloadedModels(): Promise; + deleteResources(...sources: ResourceSource[]): Promise; + getFilesTotalSize(...sources: ResourceSource[]): Promise; + handleObject(source: ResourceSource): Promise; + handleLocalFile(source: ResourceSource): string; + handleReleaseModeFile( + sourceExtended: ResourceSourceExtended + ): Promise; + handleDevModeFile( + sourceExtended: ResourceSourceExtended + ): Promise; + handleRemoteFile( + sourceExtended: ResourceSourceExtended + ): Promise; +} + +/** + * This module provides functions to download and work with downloaded files stored in the application's document directory inside the `react-native-executorch/` directory. + * These utilities can help you manage your storage and clean up the downloaded files when they are no longer needed. + * + * @category Utilities - General + */ +export const ExpoResourceFetcher: ExpoResourceFetcherInterface = { + downloads: new Map(), //map of currently downloading (or paused) files, if the download was started by .fetch() method. + + /** + * Fetches resources (remote URLs, local files or embedded assets), downloads or stores them locally for use by React Native ExecuTorch. + * + * @param callback - Optional callback to track progress of all downloads, reported between 0 and 1. + * @param sources - Multiple resources that can be strings, asset references, or objects. + * @returns If the fetch was successful, it returns a promise which resolves to an array of local file paths for the downloaded/stored resources (without file:// prefix). + * If the fetch was interrupted by `pauseFetching` or `cancelFetching`, it returns a promise which resolves to `null`. + */ + async fetch( + callback: (downloadProgress: number) => void = () => {}, + ...sources: ResourceSource[] + ) { + if (sources.length === 0) { + throw new RnExecutorchError( + RnExecutorchErrorCode.InvalidUserInput, + 'Empty list given as an argument' + ); + } + const { results: info, totalLength } = + await ResourceFetcherUtils.getFilesSizes(sources); + const head: ResourceSourceExtended = { + source: info[0]!.source, + sourceType: info[0]!.type, + callback: + info[0]!.type === SourceType.REMOTE_FILE + ? ResourceFetcherUtils.calculateDownloadProgress( + totalLength, + info[0]!.previousFilesTotalLength, + info[0]!.length, + callback + ) + : () => {}, + results: [], + }; + + let node = head; + for (let idx = 1; idx < sources.length; idx++) { + node.next = { + source: info[idx]!.source, + sourceType: info[idx]!.type, + callback: + info[idx]!.type === SourceType.REMOTE_FILE + ? ResourceFetcherUtils.calculateDownloadProgress( + totalLength, + info[idx]!.previousFilesTotalLength, + info[idx]!.length, + callback + ) + : () => {}, + results: [], + }; + node = node.next; + } + return this.singleFetch(head); + }, + + async singleFetch( + sourceExtended: ResourceSourceExtended + ): Promise { + const source = sourceExtended.source; + switch (sourceExtended.sourceType) { + case SourceType.OBJECT: { + return this.returnOrStartNext( + sourceExtended, + await this.handleObject(source) + ); + } + case SourceType.LOCAL_FILE: { + return this.returnOrStartNext( + sourceExtended, + this.handleLocalFile(source) + ); + } + case SourceType.RELEASE_MODE_FILE: { + return this.returnOrStartNext( + sourceExtended, + await this.handleReleaseModeFile(sourceExtended) + ); + } + case SourceType.DEV_MODE_FILE: { + const result = await this.handleDevModeFile(sourceExtended); + if (result !== null) { + return this.returnOrStartNext(sourceExtended, result); + } + return null; + } + default: { + //case SourceType.REMOTE_FILE + const result = await this.handleRemoteFile(sourceExtended); + if (result !== null) { + return this.returnOrStartNext(sourceExtended, result); + } + return null; + } + } + }, + + //if any download ends successfully this function is called - it checks whether it should trigger next download or return list of paths. + returnOrStartNext(sourceExtended: ResourceSourceExtended, result: string) { + sourceExtended.results.push(result); + + if (sourceExtended.next) { + const nextSource = sourceExtended.next; + nextSource.results.push(...sourceExtended.results); + return this.singleFetch(nextSource); + } + sourceExtended.callback!(1); + return sourceExtended.results; + }, + + async pause(source: ResourceSource) { + const resource = this.downloads.get(source)!; + switch (resource.status) { + case DownloadStatus.PAUSED: + throw new RnExecutorchError( + RnExecutorchErrorCode.ResourceFetcherAlreadyPaused, + "The file download is currently paused. Can't pause the download of the same file twice." + ); + default: { + resource.status = DownloadStatus.PAUSED; + await resource.downloadResumable.pauseAsync(); + } + } + }, + + async resume(source: ResourceSource) { + const resource = this.downloads.get(source)!; + if ( + !resource.extendedInfo.fileUri || + !resource.extendedInfo.cacheFileUri || + !resource.extendedInfo.uri + ) { + throw new RnExecutorchError( + RnExecutorchErrorCode.ResourceFetcherMissingUri, + 'Something went wrong. File uri info is not specified' + ); + } + switch (resource.status) { + case DownloadStatus.ONGOING: + throw new RnExecutorchError( + RnExecutorchErrorCode.ResourceFetcherAlreadyOngoing, + "The file download is currently ongoing. Can't resume the ongoing download." + ); + default: { + resource.status = DownloadStatus.ONGOING; + const result = await resource.downloadResumable.resumeAsync(); + if ( + !this.downloads.has(source) || + this.downloads.get(source)!.status === DownloadStatus.PAUSED + ) { + //if canceled or paused after earlier resuming. + return null; + } + if ( + !result || + (result.status !== HTTP_CODE.OK && + result.status !== HTTP_CODE.PARTIAL_CONTENT) + ) { + throw new RnExecutorchError( + RnExecutorchErrorCode.ResourceFetcherDownloadFailed, + `Failed to fetch resource from '${resource.extendedInfo.uri}, context: ${result}'` + ); + } + await moveAsync({ + from: resource.extendedInfo.cacheFileUri, + to: resource.extendedInfo.fileUri, + }); + this.downloads.delete(source); + ResourceFetcherUtils.triggerHuggingFaceDownloadCounter( + resource.extendedInfo.uri + ); + + return this.returnOrStartNext( + resource.extendedInfo, + ResourceFetcherUtils.removeFilePrefix(resource.extendedInfo.fileUri) + ); + } + } + }, + + async cancel(source: ResourceSource) { + const resource = this.downloads.get(source)!; + await resource.downloadResumable.cancelAsync(); + this.downloads.delete(source); + }, + + /** + * Pauses an ongoing download of files. + * + * @param sources - The resource identifiers used when calling `fetch`. + * @returns A promise that resolves once the download is paused. + */ + async pauseFetching(...sources: ResourceSource[]) { + const source = this.findActive(sources); + await this.pause(source); + }, + + /** + * Resumes a paused download of files. + * + * @param sources - The resource identifiers used when calling fetch. + * @returns If the fetch was successful, it returns a promise which resolves to an array of local file paths for the downloaded resources (without file:// prefix). + * If the fetch was again interrupted by `pauseFetching` or `cancelFetching`, it returns a promise which resolves to `null`. + */ + async resumeFetching(...sources: ResourceSource[]) { + const source = this.findActive(sources); + await this.resume(source); + }, + + /** + * Cancels an ongoing/paused download of files. + * + * @param sources - The resource identifiers used when calling `fetch()`. + * @returns A promise that resolves once the download is canceled. + */ + async cancelFetching(...sources: ResourceSource[]) { + const source = this.findActive(sources); + await this.cancel(source); + }, + + findActive(sources: ResourceSource[]) { + for (const source of sources) { + if (this.downloads.has(source)) { + return source; + } + } + throw new RnExecutorchError( + RnExecutorchErrorCode.ResourceFetcherNotActive, + 'None of given sources are currently during downloading process.' + ); + }, + + /** + * Lists all the downloaded files used by React Native ExecuTorch. + * + * @returns A promise, which resolves to an array of URIs for all the downloaded files. + */ + async listDownloadedFiles() { + const files = await readDirectoryAsync(RNEDirectory); + return files.map((file: string) => `${RNEDirectory}${file}`); + }, + + /** + * Lists all the downloaded models used by React Native ExecuTorch. + * + * @returns A promise, which resolves to an array of URIs for all the downloaded models. + */ + async listDownloadedModels() { + const files = await this.listDownloadedFiles(); + return files.filter((file: string) => file.endsWith('.pte')); + }, + + /** + * Deletes downloaded resources from the local filesystem. + * + * @param sources - The resource identifiers used when calling `fetch`. + * @returns A promise that resolves once all specified resources have been removed. + */ + async deleteResources(...sources: ResourceSource[]) { + for (const source of sources) { + const filename = ResourceFetcherUtils.getFilenameFromUri( + source as string + ); + const fileUri = `${RNEDirectory}${filename}`; + if (await ResourceFetcherUtils.checkFileExists(fileUri)) { + await deleteAsync(fileUri); + } + } + }, + + /** + * Fetches the info about files size. Works only for remote files. + * + * @param sources - The resource identifiers (URLs). + * @returns A promise that resolves to combined size of files in bytes. + */ + async getFilesTotalSize(...sources: ResourceSource[]) { + return (await ResourceFetcherUtils.getFilesSizes(sources)).totalLength; + }, + + async handleObject(source: ResourceSource) { + if (typeof source !== 'object') { + throw new RnExecutorchError( + RnExecutorchErrorCode.InvalidModelSource, + 'Source is expected to be number' + ); + } + const jsonString = JSON.stringify(source); + const digest = ResourceFetcherUtils.hashObject(jsonString); + const filename = `${digest}.json`; + const path = `${RNEDirectory}${filename}`; + + if (await ResourceFetcherUtils.checkFileExists(path)) { + return ResourceFetcherUtils.removeFilePrefix(path); + } + + await ResourceFetcherUtils.createDirectoryIfNoExists(); + await writeAsStringAsync(path, jsonString, { + encoding: EncodingType.UTF8, + }); + + return ResourceFetcherUtils.removeFilePrefix(path); + }, + + handleLocalFile(source: ResourceSource) { + if (typeof source !== 'string') { + throw new RnExecutorchError( + RnExecutorchErrorCode.InvalidModelSource, + 'Source is expected to be string' + ); + } + return ResourceFetcherUtils.removeFilePrefix(source); + }, + + async handleReleaseModeFile(sourceExtended: ResourceSourceExtended) { + const source = sourceExtended.source; + if (typeof source !== 'number') { + throw new RnExecutorchError( + RnExecutorchErrorCode.InvalidModelSource, + 'Source is expected to be number' + ); + } + const asset = Asset.fromModule(source); + const uri = asset.uri; + const filename = ResourceFetcherUtils.getFilenameFromUri(uri); + const fileUri = `${RNEDirectory}${filename}`; + // On Android, file uri does not contain file extension, so we add it manually + const fileUriWithType = + Platform.OS === 'android' ? `${fileUri}.${asset.type}` : fileUri; + if (await ResourceFetcherUtils.checkFileExists(fileUri)) { + return ResourceFetcherUtils.removeFilePrefix(fileUri); + } + await ResourceFetcherUtils.createDirectoryIfNoExists(); + await copyAsync({ + from: asset.uri, + to: fileUriWithType, + }); + return ResourceFetcherUtils.removeFilePrefix(fileUriWithType); + }, + + async handleDevModeFile(sourceExtended: ResourceSourceExtended) { + const source = sourceExtended.source; + if (typeof source !== 'number') { + throw new RnExecutorchError( + RnExecutorchErrorCode.InvalidModelSource, + 'Source is expected to be a number' + ); + } + sourceExtended.uri = Asset.fromModule(source).uri; + return await this.handleRemoteFile(sourceExtended); + }, + + async handleRemoteFile(sourceExtended: ResourceSourceExtended) { + const source = sourceExtended.source; + if (typeof source === 'object') { + throw new RnExecutorchError( + RnExecutorchErrorCode.InvalidModelSource, + 'Source is expected to be a string or a number' + ); + } + if (this.downloads.has(source)) { + const resource = this.downloads.get(source)!; + if (resource.status === DownloadStatus.PAUSED) { + // if the download is paused, `fetch` is treated like `resume` + this.resume(source); + } + // if the download is ongoing, throw error. + throw new RnExecutorchError( + RnExecutorchErrorCode.ResourceFetcherDownloadInProgress, + 'Already downloading this file' + ); + } + if (typeof source === 'number' && !sourceExtended.uri) { + throw new RnExecutorchError( + RnExecutorchErrorCode.ResourceFetcherMissingUri, + 'Source Uri is expected to be available here' + ); + } + if (typeof source === 'string') { + sourceExtended.uri = source; + } + const uri = sourceExtended.uri!; + const filename = ResourceFetcherUtils.getFilenameFromUri(uri); + sourceExtended.fileUri = `${RNEDirectory}${filename}`; + sourceExtended.cacheFileUri = `${cacheDirectory}${filename}`; + + if (await ResourceFetcherUtils.checkFileExists(sourceExtended.fileUri)) { + return ResourceFetcherUtils.removeFilePrefix(sourceExtended.fileUri); + } + await ResourceFetcherUtils.createDirectoryIfNoExists(); + + const downloadResumable = createDownloadResumable( + uri, + sourceExtended.cacheFileUri, + { sessionType: FileSystemSessionType.BACKGROUND }, + ({ + totalBytesWritten, + totalBytesExpectedToWrite, + }: { + totalBytesWritten: number; + totalBytesExpectedToWrite: number; + }) => { + if (totalBytesExpectedToWrite === -1) { + // If totalBytesExpectedToWrite is -1, it means the server does not provide content length. + sourceExtended.callback!(0); + return; + } + sourceExtended.callback!(totalBytesWritten / totalBytesExpectedToWrite); + } + ); + //create value for the this.download Map + const downloadResource: DownloadResource = { + downloadResumable: downloadResumable, + status: DownloadStatus.ONGOING, + extendedInfo: sourceExtended, + }; + //add key-value pair to map + this.downloads.set(source, downloadResource); + const result = await downloadResumable.downloadAsync(); + if ( + !this.downloads.has(source) || + this.downloads.get(source)!.status === DownloadStatus.PAUSED + ) { + // if canceled or paused during the download + return null; + } + if (!result || result.status !== HTTP_CODE.OK) { + throw new RnExecutorchError( + RnExecutorchErrorCode.ResourceFetcherDownloadFailed, + `Failed to fetch resource from '${source}, context: ${result}'` + ); + } + await moveAsync({ + from: sourceExtended.cacheFileUri, + to: sourceExtended.fileUri, + }); + this.downloads.delete(source); + ResourceFetcherUtils.triggerHuggingFaceDownloadCounter(uri); + return ResourceFetcherUtils.removeFilePrefix(sourceExtended.fileUri); + }, + + /** + * Reads the contents of a file as a string. + * + * @param path - Absolute file path or file URI to read. + * @returns A promise that resolves to the file contents as a string. + * + * @remarks + * **REQUIRED**: Used internally for reading configuration files (e.g., tokenizer configs). + * + * **Technical Note**: Expo requires file URIs (file:// prefix), so this method + * automatically converts plain paths to URIs if needed. + */ + async readAsString(path: string) { + // Expo needs URI + const uri = path.startsWith('file://') ? path : `file://${path}`; + return await readAsStringAsync(uri); + }, +}; diff --git a/packages/expo-resource-fetcher/src/ResourceFetcherUtils.ts b/packages/expo-resource-fetcher/src/ResourceFetcherUtils.ts new file mode 100644 index 000000000..b193afb69 --- /dev/null +++ b/packages/expo-resource-fetcher/src/ResourceFetcherUtils.ts @@ -0,0 +1,116 @@ +import { RNEDirectory } from './constants/directories'; +import { + ResourceSource, + Logger, + ResourceFetcherUtils as CoreUtils, + HTTP_CODE, + DownloadStatus, + SourceType, + ResourceSourceExtended, + RnExecutorchError, + RnExecutorchErrorCode, +} from 'react-native-executorch'; +import { Asset } from 'expo-asset'; + +/** + * @internal + */ +import { + getInfoAsync, + makeDirectoryAsync, + type DownloadResumable, +} from 'expo-file-system/legacy'; + +export { HTTP_CODE, DownloadStatus, SourceType, ResourceSourceExtended }; + +export interface DownloadResource { + downloadResumable: DownloadResumable; + status: DownloadStatus; + extendedInfo: ResourceSourceExtended; +} + +export namespace ResourceFetcherUtils { + export const removeFilePrefix = CoreUtils.removeFilePrefix; + export const hashObject = CoreUtils.hashObject; + export const calculateDownloadProgress = CoreUtils.calculateDownloadProgress; + export const triggerHuggingFaceDownloadCounter = + CoreUtils.triggerHuggingFaceDownloadCounter; + export const getFilenameFromUri = CoreUtils.getFilenameFromUri; + + export function getType(source: ResourceSource): SourceType { + if (typeof source === 'object') { + return SourceType.OBJECT; + } else if (typeof source === 'number') { + const uri = Asset.fromModule(source).uri; + if (uri.startsWith('http')) { + return SourceType.DEV_MODE_FILE; + } + return SourceType.RELEASE_MODE_FILE; + } + // typeof source == 'string' + if (source.startsWith('file://')) { + return SourceType.LOCAL_FILE; + } + return SourceType.REMOTE_FILE; + } + + export async function getFilesSizes(sources: ResourceSource[]) { + const results: Array<{ + source: ResourceSource; + type: SourceType; + length: number; + previousFilesTotalLength: number; + }> = []; + let totalLength = 0; + let previousFilesTotalLength = 0; + for (const source of sources) { + const type = ResourceFetcherUtils.getType(source); + let length = 0; + try { + if (type === SourceType.REMOTE_FILE && typeof source === 'string') { + const response = await fetch(source, { method: 'HEAD' }); + if (!response.ok) { + Logger.warn( + `Failed to fetch HEAD for ${source}: ${response.status}` + ); + continue; + } + + const contentLength = response.headers.get('content-length'); + if (!contentLength) { + Logger.warn(`No content-length header for ${source}`); + } + + length = contentLength ? parseInt(contentLength, 10) : 0; + previousFilesTotalLength = totalLength; + totalLength += length; + } + } catch (error) { + Logger.warn(`Error fetching HEAD for ${source}:`, error); + continue; + } finally { + results.push({ source, type, length, previousFilesTotalLength }); + } + } + return { results, totalLength }; + } + + export async function createDirectoryIfNoExists() { + if (!(await checkFileExists(RNEDirectory))) { + try { + await makeDirectoryAsync(RNEDirectory, { intermediates: true }); + } catch (error) { + throw new RnExecutorchError( + RnExecutorchErrorCode.FileWriteFailed, + `Failed to create directory at ${RNEDirectory}`, + error + ); + } + } + } + + export async function checkFileExists(fileUri: string) { + const fileInfo = await getInfoAsync(fileUri); + return fileInfo.exists; + } +} diff --git a/packages/react-native-executorch/src/constants/directories.ts b/packages/expo-resource-fetcher/src/constants/directories.ts similarity index 100% rename from packages/react-native-executorch/src/constants/directories.ts rename to packages/expo-resource-fetcher/src/constants/directories.ts diff --git a/packages/expo-resource-fetcher/src/index.ts b/packages/expo-resource-fetcher/src/index.ts new file mode 100644 index 000000000..c0ec4024f --- /dev/null +++ b/packages/expo-resource-fetcher/src/index.ts @@ -0,0 +1 @@ +export * from './ResourceFetcher'; diff --git a/packages/expo-resource-fetcher/tsconfig.json b/packages/expo-resource-fetcher/tsconfig.json new file mode 100644 index 000000000..cadd2509a --- /dev/null +++ b/packages/expo-resource-fetcher/tsconfig.json @@ -0,0 +1,28 @@ +{ + "extends": "../../tsconfig.json", + "compilerOptions": { + "rootDir": "src", + "outDir": "lib", + "declaration": true, + "declarationMap": true, + "tsBuildInfoFile": "./lib/typescript/tsconfig.tsbuildinfo", + "composite": true, + "allowJs": false, + "allowUnreachableCode": false, + "allowUnusedLabels": false, + "esModuleInterop": true, + "forceConsistentCasingInFileNames": true, + "module": "esnext", + "moduleResolution": "node", + "noFallthroughCasesInSwitch": true, + "noImplicitReturns": true, + "noStrictGenericChecks": false, + "noUnusedLocals": true, + "noUnusedParameters": true, + "noUncheckedIndexedAccess": true, + "strict": true, + "types": ["react", "node"] + }, + "include": ["src"], + "exclude": ["node_modules", "lib"] +} diff --git a/packages/react-native-executorch/common/rnexecutorch/ErrorCodes.h b/packages/react-native-executorch/common/rnexecutorch/ErrorCodes.h index 6ca7b8581..67748d716 100644 --- a/packages/react-native-executorch/common/rnexecutorch/ErrorCodes.h +++ b/packages/react-native-executorch/common/rnexecutorch/ErrorCodes.h @@ -9,27 +9,33 @@ namespace rnexecutorch { enum class RnExecutorchErrorCode : int32_t { /** - * An umbrella-error that is thrown usually when something unexpected happens, for example a 3rd-party library error. + * An umbrella-error that is thrown usually when something unexpected happens, + * for example a 3rd-party library error. */ UnknownError = 101, /** - * Thrown when a user tries to run a model that is not yet downloaded or loaded into memory. + * Thrown when a user tries to run a model that is not yet downloaded or + * loaded into memory. */ ModuleNotLoaded = 102, /** - * An error ocurred when saving a file. This could be, for instance a result image from an image model. + * An error ocurred when saving a file. This could be, for instance a result + * image from an image model. */ FileWriteFailed = 103, /** - * Thrown when a user tries to run a model that is currently processing. It is only allowed to run a single model prediction at a time. + * Thrown when a user tries to run a model that is currently processing. It is + * only allowed to run a single model prediction at a time. */ ModelGenerating = 104, /** - * Thrown when a language is passed to a multi-language model that is not supported. For example OCR or Speech To Text. + * Thrown when a language is passed to a multi-language model that is not + * supported. For example OCR or Speech To Text. */ LanguageNotSupported = 105, /** - * Thrown when config parameters passed to a model are invalid. For example, when LLM's topp is outside of range [0, 1]. + * Thrown when config parameters passed to a model are invalid. For example, + * when LLM's topp is outside of range [0, 1]. */ InvalidConfig = 112, /** @@ -37,7 +43,8 @@ enum class RnExecutorchErrorCode : int32_t { */ InvalidModelSource = 255, /** - * Thrown when the number of passed inputs to the model is different than the model metadata specifies. + * Thrown when the number of passed inputs to the model is different than the + * model metadata specifies. */ UnexpectedNumInputs = 97, /** @@ -45,7 +52,8 @@ enum class RnExecutorchErrorCode : int32_t { */ ThreadPoolError = 113, /** - * Thrown when a file read operation failed. This could be invalid image url passed to image models, or unsupported format. + * Thrown when a file read operation failed. This could be invalid image url + * passed to image models, or unsupported format. */ FileReadFailed = 114, /** @@ -53,7 +61,8 @@ enum class RnExecutorchErrorCode : int32_t { */ InvalidModelOutput = 115, /** - * Thrown when the dimensions of input tensors don't match the model's expected dimensions. + * Thrown when the dimensions of input tensors don't match the model's + * expected dimensions. */ WrongDimensions = 116, /** @@ -62,7 +71,8 @@ enum class RnExecutorchErrorCode : int32_t { */ InvalidUserInput = 117, /** - * Thrown when the number of downloaded files is unexpected, due to download interruptions. + * Thrown when the number of downloaded files is unexpected, due to download + * interruptions. */ DownloadInterrupted = 118, /** @@ -75,19 +85,23 @@ enum class RnExecutorchErrorCode : int32_t { */ MultilingualConfiguration = 160, /** - * Thrown when streaming transcription is attempted but audio data chunk is missing. + * Thrown when streaming transcription is attempted but audio data chunk is + * missing. */ MissingDataChunk = 161, /** - * Thrown when trying to stop or insert data into a stream that hasn't been started. + * Thrown when trying to stop or insert data into a stream that hasn't been + * started. */ StreamingNotStarted = 162, /** - * Thrown when trying to start a new streaming session while another is already in progress. + * Thrown when trying to start a new streaming session while another is + * already in progress. */ StreamingInProgress = 163, /** - * Thrown when a resource fails to download. This could be due to invalid URL, or for example a network problem. + * Thrown when a resource fails to download. This could be due to invalid URL, + * or for example a network problem. */ ResourceFetcherDownloadFailed = 180, /** @@ -103,7 +117,8 @@ enum class RnExecutorchErrorCode : int32_t { */ ResourceFetcherAlreadyOngoing = 183, /** - * Thrown when trying to pause, resume, or cancel a download that is not active. + * Thrown when trying to pause, resume, or cancel a download that is not + * active. */ ResourceFetcherNotActive = 184, /** diff --git a/packages/react-native-executorch/common/rnexecutorch/tests/unit/FileUtilsTest.cpp b/packages/react-native-executorch/common/rnexecutorch/tests/unit/FileUtilsTest.cpp index ed9d80236..c25f2c197 100644 --- a/packages/react-native-executorch/common/rnexecutorch/tests/unit/FileUtilsTest.cpp +++ b/packages/react-native-executorch/common/rnexecutorch/tests/unit/FileUtilsTest.cpp @@ -26,7 +26,7 @@ TEST_F(FileIOTest, LoadBytesFromFileSuccessfully) { } TEST_F(FileIOTest, LoadBytesFromFileFailOnNonExistentFile) { - EXPECT_THROW( - { loadBytesFromFile("non_existent_file.txt"); }, RnExecutorchError); + EXPECT_THROW({ loadBytesFromFile("non_existent_file.txt"); }, + RnExecutorchError); } } // namespace rnexecutorch::file_utils diff --git a/packages/react-native-executorch/package.json b/packages/react-native-executorch/package.json index 0541d03cb..0d455102d 100644 --- a/packages/react-native-executorch/package.json +++ b/packages/react-native-executorch/package.json @@ -66,9 +66,6 @@ "registry": "https://registry.npmjs.org/" }, "peerDependencies": { - "expo": ">=54.0.0", - "expo-asset": "^12.0.0", - "expo-file-system": "^19.0.0", "react": "*", "react-native": "*" }, @@ -76,9 +73,6 @@ "@react-native-community/cli": "latest", "@types/jest": "^29.5.5", "@types/react": "~19.1.10", - "expo": "^54.0.0", - "expo-asset": "12.0.11", - "expo-file-system": "^19.0.20", "jest": "^29.7.0", "metro-react-native-babel-preset": "^0.77.0", "react": "19.1.0", diff --git a/packages/react-native-executorch/src/controllers/LLMController.ts b/packages/react-native-executorch/src/controllers/LLMController.ts index 62b438fab..92bb2e7e0 100644 --- a/packages/react-native-executorch/src/controllers/LLMController.ts +++ b/packages/react-native-executorch/src/controllers/LLMController.ts @@ -12,7 +12,6 @@ import { } from '../types/llm'; import { parseToolCall } from '../utils/llm'; import { Logger } from '../common/Logger'; -import { readAsStringAsync } from 'expo-file-system/legacy'; import { RnExecutorchError, parseUnknownError } from '../errors/errorUtils'; import { RnExecutorchErrorCode } from '../errors/ErrorCodes'; @@ -117,7 +116,7 @@ export class LLMController { } this.tokenizerConfig = JSON.parse( - await readAsStringAsync('file://' + tokenizerConfigPath!) + await ResourceFetcher.fs.readAsString(tokenizerConfigPath!) ); this.nativeModule = global.loadLLM(modelPath, tokenizerPath); this.isReadyCallback(true); diff --git a/packages/react-native-executorch/src/index.ts b/packages/react-native-executorch/src/index.ts index a42881f45..72859fde5 100644 --- a/packages/react-native-executorch/src/index.ts +++ b/packages/react-native-executorch/src/index.ts @@ -1,4 +1,20 @@ import { ETInstallerNativeModule } from './native/RnExecutorchModules'; +import { + ResourceFetcher, + ResourceFetcherAdapter, +} from './utils/ResourceFetcher'; + +export interface ExecutorchConfig { + resourceFetcher: ResourceFetcherAdapter; +} + +export function initExecutorch(config: ExecutorchConfig) { + ResourceFetcher.setAdapter(config.resourceFetcher); +} + +export function cleanupExecutorch() { + ResourceFetcher.resetAdapter(); +} // eslint-disable no-var declare global { @@ -113,7 +129,9 @@ export * from './modules/general/ExecutorchModule'; // utils export * from './utils/ResourceFetcher'; +export * from './utils/ResourceFetcherUtils'; export * from './utils/llm'; +export * from './common/Logger'; // types export * from './types/objectDetection'; diff --git a/packages/react-native-executorch/src/utils/ResourceFetcher.ts b/packages/react-native-executorch/src/utils/ResourceFetcher.ts index f17e14ae0..6b7e33827 100644 --- a/packages/react-native-executorch/src/utils/ResourceFetcher.ts +++ b/packages/react-native-executorch/src/utils/ResourceFetcher.ts @@ -1,509 +1,131 @@ -/** - * Resource Fetcher - * - * Provides an interface for downloading files (via `ResourceFetcher.fetch()`) - * - * Key functionality: - * - Download control: pause, resume, and cancel operations through: - * - Single file: `.pauseFetching()`, `.resumeFetching()`, `.cancelFetching()` - * - Downloaded file management: - * - `.getFilesTotalSize()`, `.listDownloadedFiles()`, `.listDownloadedModels()`, `.deleteResources()` - * - * Remark: The pausing/resuming/canceling works only for fetching remote resources. - * - * Most exported functions accept: - * - Multiple `ResourceSource` arguments, (union type of string, number or object) - * - * Method `.fetch()` takes argument as callback that reports download progress. - * Method`.fetch()` returns array of paths to successfully saved files or null if the download was paused or cancelled (then resume functions can return paths). - * - * Technical Implementation: - * - Maintains a `downloads` Map instance that tracks: - * - Currently downloading resources - * - Paused downloads - * - Successful downloads are automatically removed from the `downloads` Map - * - Uses the `ResourceSourceExtended` interface to enable pause/resume functionality: - * - Wraps user-provided `ResourceSource` elements - * - Implements linked list behavior via the `.next` attribute - * - Automatically processes subsequent downloads when `.next` contains a valid resource - */ - -import { - cacheDirectory, - copyAsync, - createDownloadResumable, - moveAsync, - FileSystemSessionType, - writeAsStringAsync, - EncodingType, - deleteAsync, - readDirectoryAsync, -} from 'expo-file-system/legacy'; -import { Asset } from 'expo-asset'; -import { Platform } from 'react-native'; -import { RNEDirectory } from '../constants/directories'; import { ResourceSource } from '../types/common'; -import { - ResourceFetcherUtils, - HTTP_CODE, - DownloadStatus, - SourceType, - ResourceSourceExtended, - DownloadResource, -} from './ResourceFetcherUtils'; -import { RnExecutorchErrorCode } from '../errors/ErrorCodes'; import { RnExecutorchError } from '../errors/errorUtils'; +import { RnExecutorchErrorCode } from '../errors/ErrorCodes'; /** - * This module provides functions to download and work with downloaded files stored in the application's document directory inside the `react-native-executorch/` directory. - * These utilities can help you manage your storage and clean up the downloaded files when they are no longer needed. + * Adapter interface for resource fetching operations. * - * @category Utilities - General + * **Required Methods:** + * - {@link fetch}: Download resources to local storage (used by all modules) + * - {@link readAsString}: Read file contents as string (used for config files) + * + * @remarks + * This interface is intentionally minimal. Custom fetchers only need to implement + * these two methods for the library to function correctly. */ -export class ResourceFetcher { - static downloads = new Map(); //map of currently downloading (or paused) files, if the download was started by .fetch() method. - +export interface ResourceFetcherAdapter { /** * Fetches resources (remote URLs, local files or embedded assets), downloads or stores them locally for use by React Native ExecuTorch. * * @param callback - Optional callback to track progress of all downloads, reported between 0 and 1. * @param sources - Multiple resources that can be strings, asset references, or objects. * @returns If the fetch was successful, it returns a promise which resolves to an array of local file paths for the downloaded/stored resources (without file:// prefix). - * If the fetch was interrupted by `pauseFetching` or `cancelFetching`, it returns a promise which resolves to `null`. - */ - static async fetch( - callback: (downloadProgress: number) => void = () => {}, - ...sources: ResourceSource[] - ) { - if (sources.length === 0) { - throw new RnExecutorchError( - RnExecutorchErrorCode.InvalidUserInput, - 'Empty list given as an argument' - ); - } - const { results: info, totalLength } = - await ResourceFetcherUtils.getFilesSizes(sources); - const head: ResourceSourceExtended = { - source: info[0]!.source, - sourceType: info[0]!.type, - callback: - info[0]!.type === SourceType.REMOTE_FILE - ? ResourceFetcherUtils.calculateDownloadProgress( - totalLength, - info[0]!.previousFilesTotalLength, - info[0]!.length, - callback - ) - : () => {}, - results: [], - }; - - let node = head; - for (let idx = 1; idx < sources.length; idx++) { - node.next = { - source: info[idx]!.source, - sourceType: info[idx]!.type, - callback: - info[idx]!.type === SourceType.REMOTE_FILE - ? ResourceFetcherUtils.calculateDownloadProgress( - totalLength, - info[idx]!.previousFilesTotalLength, - info[idx]!.length, - callback - ) - : () => {}, - results: [], - }; - node = node.next; - } - return this.singleFetch(head); - } - - private static async singleFetch( - sourceExtended: ResourceSourceExtended - ): Promise { - const source = sourceExtended.source; - switch (sourceExtended.sourceType) { - case SourceType.OBJECT: { - return this.returnOrStartNext( - sourceExtended, - await this.handleObject(source) - ); - } - case SourceType.LOCAL_FILE: { - return this.returnOrStartNext( - sourceExtended, - this.handleLocalFile(source) - ); - } - case SourceType.RELEASE_MODE_FILE: { - return this.returnOrStartNext( - sourceExtended, - await this.handleReleaseModeFile(sourceExtended) - ); - } - case SourceType.DEV_MODE_FILE: { - const result = await this.handleDevModeFile(sourceExtended); - if (result !== null) { - return this.returnOrStartNext(sourceExtended, result); - } - return null; - } - default: { - //case SourceType.REMOTE_FILE - const result = await this.handleRemoteFile(sourceExtended); - if (result !== null) { - return this.returnOrStartNext(sourceExtended, result); - } - return null; - } - } - } - - //if any download ends successfully this function is called - it checks whether it should trigger next download or return list of paths. - private static returnOrStartNext( - sourceExtended: ResourceSourceExtended, - result: string - ) { - sourceExtended.results.push(result); - - if (sourceExtended.next) { - const nextSource = sourceExtended.next; - nextSource.results.push(...sourceExtended.results); - return this.singleFetch(nextSource); - } - sourceExtended.callback!(1); - return sourceExtended.results; - } - - private static async pause(source: ResourceSource) { - const resource = this.downloads.get(source)!; - switch (resource.status) { - case DownloadStatus.PAUSED: - throw new RnExecutorchError( - RnExecutorchErrorCode.ResourceFetcherAlreadyPaused, - "The file download is currently paused. Can't pause the download of the same file twice." - ); - default: { - resource.status = DownloadStatus.PAUSED; - await resource.downloadResumable.pauseAsync(); - } - } - } - - private static async resume(source: ResourceSource) { - const resource = this.downloads.get(source)!; - if ( - !resource.extendedInfo.fileUri || - !resource.extendedInfo.cacheFileUri || - !resource.extendedInfo.uri - ) { - throw new RnExecutorchError( - RnExecutorchErrorCode.ResourceFetcherMissingUri, - 'Something went wrong. File uri info is not specified' - ); - } - switch (resource.status) { - case DownloadStatus.ONGOING: - throw new RnExecutorchError( - RnExecutorchErrorCode.ResourceFetcherAlreadyOngoing, - "The file download is currently ongoing. Can't resume the ongoing download." - ); - default: { - resource.status = DownloadStatus.ONGOING; - const result = await resource.downloadResumable.resumeAsync(); - if ( - !this.downloads.has(source) || - this.downloads.get(source)!.status === DownloadStatus.PAUSED - ) { - //if canceled or paused after earlier resuming. - return null; - } - if ( - !result || - (result.status !== HTTP_CODE.OK && - result.status !== HTTP_CODE.PARTIAL_CONTENT) - ) { - throw new RnExecutorchError( - RnExecutorchErrorCode.ResourceFetcherDownloadFailed, - `Failed to fetch resource from '${resource.extendedInfo.uri}, context: ${result}'` - ); - } - await moveAsync({ - from: resource.extendedInfo.cacheFileUri, - to: resource.extendedInfo.fileUri, - }); - this.downloads.delete(source); - ResourceFetcherUtils.triggerHuggingFaceDownloadCounter( - resource.extendedInfo.uri - ); - - return this.returnOrStartNext( - resource.extendedInfo, - ResourceFetcherUtils.removeFilePrefix(resource.extendedInfo.fileUri) - ); - } - } - } - - private static async cancel(source: ResourceSource) { - const resource = this.downloads.get(source)!; - await resource.downloadResumable.cancelAsync(); - this.downloads.delete(source); - } - - /** - * Pauses an ongoing download of files. + * If the fetch was interrupted, it returns a promise which resolves to `null`. * - * @param sources - The resource identifiers used when calling `fetch`. - * @returns A promise that resolves once the download is paused. + * @remarks + * **REQUIRED**: Used by all library modules for downloading models and resources. */ - static async pauseFetching(...sources: ResourceSource[]) { - const source = this.findActive(sources); - await this.pause(source); - } + fetch( + callback: (downloadProgress: number) => void, + ...sources: ResourceSource[] + ): Promise; /** - * Resumes a paused download of files. + * Read file contents as a string. * - * @param sources - The resource identifiers used when calling fetch. - * @returns If the fetch was successful, it returns a promise which resolves to an array of local file paths for the downloaded resources (without file:// prefix). - * If the fetch was again interrupted by `pauseFetching` or `cancelFetching`, it returns a promise which resolves to `null`. - */ - static async resumeFetching(...sources: ResourceSource[]) { - const source = this.findActive(sources); - await this.resume(source); - } - - /** - * Cancels an ongoing/paused download of files. + * @param path - Absolute file path + * @returns File contents as string * - * @param sources - The resource identifiers used when calling `fetch()`. - * @returns A promise that resolves once the download is canceled. + * @remarks + * **REQUIRED**: Used internally for reading configuration files (e.g., tokenizer configs). */ - static async cancelFetching(...sources: ResourceSource[]) { - const source = this.findActive(sources); - await this.cancel(source); - } + readAsString(path: string): Promise; +} - private static findActive(sources: ResourceSource[]) { - for (const source of sources) { - if (this.downloads.has(source)) { - return source; - } - } - throw new RnExecutorchError( - RnExecutorchErrorCode.ResourceFetcherNotActive, - 'None of given sources are currently during downloading process.' - ); - } +/** + * This module provides functions to download and work with downloaded files stored in the application's document directory inside the `react-native-executorch/` directory. + * These utilities can help you manage your storage and clean up the downloaded files when they are no longer needed. + * + * @category Utilities - General + */ +export class ResourceFetcher { + private static adapter: ResourceFetcherAdapter | null = null; /** - * Lists all the downloaded files used by React Native ExecuTorch. + * Sets a custom resource fetcher adapter for resource operations. * - * @returns A promise, which resolves to an array of URIs for all the downloaded files. + * @param adapter - The adapter instance to use for fetching resources. + * + * @remarks + * **INTERNAL**: Used by platform-specific init functions (expo/bare) to inject their fetcher implementation. */ - static async listDownloadedFiles() { - const files = await readDirectoryAsync(RNEDirectory); - return files.map((file) => `${RNEDirectory}${file}`); + static setAdapter(adapter: ResourceFetcherAdapter) { + this.adapter = adapter; } /** - * Lists all the downloaded models used by React Native ExecuTorch. + * Resets the resource fetcher adapter to null. * - * @returns A promise, which resolves to an array of URIs for all the downloaded models. + * @remarks + * **INTERNAL**: Used primarily for testing purposes to clear the adapter state. */ - static async listDownloadedModels() { - const files = await this.listDownloadedFiles(); - return files.filter((file) => file.endsWith('.pte')); + static resetAdapter() { + this.adapter = null; } /** - * Deletes downloaded resources from the local filesystem. + * Gets the current resource fetcher adapter instance. + * + * @returns The configured ResourceFetcherAdapter instance. + * @throws {RnExecutorchError} If no adapter has been set via {@link setAdapter}. * - * @param sources - The resource identifiers used when calling `fetch`. - * @returns A promise that resolves once all specified resources have been removed. + * @remarks + * **INTERNAL**: Used internally by all resource fetching operations. */ - static async deleteResources(...sources: ResourceSource[]) { - for (const source of sources) { - const filename = ResourceFetcherUtils.getFilenameFromUri( - source as string + static getAdapter(): ResourceFetcherAdapter { + if (!this.adapter) { + throw new RnExecutorchError( + RnExecutorchErrorCode.NotImplemented, + 'ResourceFetcher adapter is not initialized. Please call initExecutorch({ resourceFetcher: ... }) with a valid adapter, e.g., from @react-native-executorch/expo-resource-fetcher or @react-native-executorch/bare-resource-fetcher.' ); - const fileUri = `${RNEDirectory}${filename}`; - if (await ResourceFetcherUtils.checkFileExists(fileUri)) { - await deleteAsync(fileUri); - } } + return this.adapter; } /** - * Fetches the info about files size. Works only for remote files. + * Fetches resources (remote URLs, local files or embedded assets), downloads or stores them locally for use by React Native ExecuTorch. * - * @param sources - The resource identifiers (URLs). - * @returns A promise that resolves to combined size of files in bytes. + * @param callback - Optional callback to track progress of all downloads, reported between 0 and 1. + * @param sources - Multiple resources that can be strings, asset references, or objects. + * @returns If the fetch was successful, it returns a promise which resolves to an array of local file paths for the downloaded/stored resources (without file:// prefix). + * If the fetch was interrupted, it returns a promise which resolves to `null`. */ - static async getFilesTotalSize(...sources: ResourceSource[]) { - return (await ResourceFetcherUtils.getFilesSizes(sources)).totalLength; - } - - private static async handleObject(source: ResourceSource) { - if (typeof source !== 'object') { - throw new RnExecutorchError( - RnExecutorchErrorCode.InvalidModelSource, - 'Source is expected to be object' - ); - } - const jsonString = JSON.stringify(source); - const digest = ResourceFetcherUtils.hashObject(jsonString); - const filename = `${digest}.json`; - const path = `${RNEDirectory}${filename}`; - - if (await ResourceFetcherUtils.checkFileExists(path)) { - return ResourceFetcherUtils.removeFilePrefix(path); - } - - await ResourceFetcherUtils.createDirectoryIfNoExists(); - await writeAsStringAsync(path, jsonString, { - encoding: EncodingType.UTF8, - }); - - return ResourceFetcherUtils.removeFilePrefix(path); - } - - private static handleLocalFile(source: ResourceSource) { - if (typeof source !== 'string') { - throw new RnExecutorchError( - RnExecutorchErrorCode.InvalidModelSource, - 'Source is expected to be string' - ); - } - return ResourceFetcherUtils.removeFilePrefix(source); - } - - private static async handleReleaseModeFile( - sourceExtended: ResourceSourceExtended - ) { - const source = sourceExtended.source; - if (typeof source !== 'number') { - throw new RnExecutorchError( - RnExecutorchErrorCode.InvalidModelSource, - 'Source is expected to be number' - ); - } - const asset = Asset.fromModule(source); - const uri = asset.uri; - const filename = ResourceFetcherUtils.getFilenameFromUri(uri); - const fileUri = `${RNEDirectory}${filename}`; - // On Android, file uri does not contain file extension, so we add it manually - const fileUriWithType = - Platform.OS === 'android' ? `${fileUri}.${asset.type}` : fileUri; - if (await ResourceFetcherUtils.checkFileExists(fileUri)) { - return ResourceFetcherUtils.removeFilePrefix(fileUri); - } - await ResourceFetcherUtils.createDirectoryIfNoExists(); - await copyAsync({ - from: asset.uri, - to: fileUriWithType, - }); - return ResourceFetcherUtils.removeFilePrefix(fileUriWithType); - } - - private static async handleDevModeFile( - sourceExtended: ResourceSourceExtended + static async fetch( + callback: (downloadProgress: number) => void = () => {}, + ...sources: ResourceSource[] ) { - const source = sourceExtended.source; - if (typeof source !== 'number') { - throw new RnExecutorchError( - RnExecutorchErrorCode.InvalidModelSource, - 'Source is expected to be a number' - ); - } - sourceExtended.uri = Asset.fromModule(source).uri; - return await this.handleRemoteFile(sourceExtended); + return this.getAdapter().fetch(callback, ...sources); } - private static async handleRemoteFile( - sourceExtended: ResourceSourceExtended - ) { - const source = sourceExtended.source; - if (typeof source === 'object') { - throw new RnExecutorchError( - RnExecutorchErrorCode.InvalidModelSource, - 'Source is expected to be a string or a number' - ); - } - if (this.downloads.has(source)) { - const resource = this.downloads.get(source)!; - if (resource.status === DownloadStatus.PAUSED) { - // if the download is paused, `fetch` is treated like `resume` - this.resume(source); - } - // if the download is ongoing, throw error. - throw new RnExecutorchError( - RnExecutorchErrorCode.ResourceFetcherDownloadInProgress, - 'Already downloading this file' - ); - } - if (typeof source === 'number' && !sourceExtended.uri) { - throw new RnExecutorchError( - RnExecutorchErrorCode.ResourceFetcherMissingUri, - 'Source Uri is expected to be available here' - ); - } - if (typeof source === 'string') { - sourceExtended.uri = source; - } - const uri = sourceExtended.uri!; - const filename = ResourceFetcherUtils.getFilenameFromUri(uri); - sourceExtended.fileUri = `${RNEDirectory}${filename}`; - sourceExtended.cacheFileUri = `${cacheDirectory}${filename}`; - - if (await ResourceFetcherUtils.checkFileExists(sourceExtended.fileUri)) { - return ResourceFetcherUtils.removeFilePrefix(sourceExtended.fileUri); - } - await ResourceFetcherUtils.createDirectoryIfNoExists(); - - const downloadResumable = createDownloadResumable( - uri, - sourceExtended.cacheFileUri, - { sessionType: FileSystemSessionType.BACKGROUND }, - ({ totalBytesWritten, totalBytesExpectedToWrite }) => { - if (totalBytesExpectedToWrite === -1) { - // If totalBytesExpectedToWrite is -1, it means the server does not provide content length. - sourceExtended.callback!(0); - return; - } - sourceExtended.callback!(totalBytesWritten / totalBytesExpectedToWrite); - } - ); - //create value for the this.download Map - const downloadResource: DownloadResource = { - downloadResumable: downloadResumable, - status: DownloadStatus.ONGOING, - extendedInfo: sourceExtended, - }; - //add key-value pair to map - this.downloads.set(source, downloadResource); - const result = await downloadResumable.downloadAsync(); - if ( - !this.downloads.has(source) || - this.downloads.get(source)!.status === DownloadStatus.PAUSED - ) { - // if canceled or paused during the download - return null; - } - if (!result || result.status !== HTTP_CODE.OK) { - throw new RnExecutorchError( - RnExecutorchErrorCode.ResourceFetcherDownloadFailed, - `Failed to fetch resource from '${source}, context: ${result}'` - ); - } - await moveAsync({ - from: sourceExtended.cacheFileUri, - to: sourceExtended.fileUri, - }); - this.downloads.delete(source); - ResourceFetcherUtils.triggerHuggingFaceDownloadCounter(uri); - return ResourceFetcherUtils.removeFilePrefix(sourceExtended.fileUri); - } + /** + * Filesystem utilities for reading downloaded resources. + * + * @remarks + * Provides access to filesystem operations through the configured adapter. + * Currently supports reading file contents as strings for configuration files. + */ + static fs = { + /** + * Reads the contents of a file as a string. + * + * @param path - Absolute file path to read. + * @returns A promise that resolves to the file contents as a string. + * + * @remarks + * **REQUIRED**: Used internally for reading configuration files (e.g., tokenizer configs). + */ + readAsString: async (path: string) => { + return this.getAdapter().readAsString(path); + }, + }; } diff --git a/packages/react-native-executorch/src/utils/ResourceFetcherUtils.ts b/packages/react-native-executorch/src/utils/ResourceFetcherUtils.ts index 207adce9e..ae60b4ab0 100644 --- a/packages/react-native-executorch/src/utils/ResourceFetcherUtils.ts +++ b/packages/react-native-executorch/src/utils/ResourceFetcherUtils.ts @@ -1,28 +1,16 @@ -import { RNEDirectory } from '../constants/directories'; -import { ResourceSource } from '../types/common'; -import { Asset } from 'expo-asset'; -import { Logger } from '../common/Logger'; +import { ResourceSource } from '..'; -/** - * @internal - */ -import { - getInfoAsync, - makeDirectoryAsync, - type DownloadResumable, -} from 'expo-file-system/legacy'; - -export const enum HTTP_CODE { +export enum HTTP_CODE { OK = 200, PARTIAL_CONTENT = 206, } -export const enum DownloadStatus { +export enum DownloadStatus { ONGOING, PAUSED, } -export const enum SourceType { +export enum SourceType { OBJECT, LOCAL_FILE, RELEASE_MODE_FILE, @@ -41,71 +29,7 @@ export interface ResourceSourceExtended { next?: ResourceSourceExtended; } -export interface DownloadResource { - downloadResumable: DownloadResumable; - status: DownloadStatus; - extendedInfo: ResourceSourceExtended; -} - export namespace ResourceFetcherUtils { - export function getType(source: ResourceSource): SourceType { - if (typeof source === 'object') { - return SourceType.OBJECT; - } else if (typeof source === 'number') { - const uri = Asset.fromModule(source).uri; - if (uri.startsWith('http')) { - return SourceType.DEV_MODE_FILE; - } - return SourceType.RELEASE_MODE_FILE; - } - // typeof source == 'string' - if (source.startsWith('file://')) { - return SourceType.LOCAL_FILE; - } - return SourceType.REMOTE_FILE; - } - - export async function getFilesSizes(sources: ResourceSource[]) { - const results: Array<{ - source: ResourceSource; - type: SourceType; - length: number; - previousFilesTotalLength: number; - }> = []; - let totalLength = 0; - let previousFilesTotalLength = 0; - for (const source of sources) { - const type = ResourceFetcherUtils.getType(source); - let length = 0; - try { - if (type === SourceType.REMOTE_FILE && typeof source === 'string') { - const response = await fetch(source, { method: 'HEAD' }); - if (!response.ok) { - Logger.warn( - `Failed to fetch HEAD for ${source}: ${response.status}` - ); - continue; - } - - const contentLength = response.headers.get('content-length'); - if (!contentLength) { - Logger.warn(`No content-length header for ${source}`); - } - - length = contentLength ? parseInt(contentLength, 10) : 0; - previousFilesTotalLength = totalLength; - totalLength += length; - } - } catch (error) { - Logger.warn(`Error fetching HEAD for ${source}:`, error); - continue; - } finally { - results.push({ source, type, length, previousFilesTotalLength }); - } - } - return { results, totalLength }; - } - export function removeFilePrefix(uri: string) { return uri.startsWith('file://') ? uri.slice(7) : uri; } @@ -165,17 +89,6 @@ export namespace ResourceFetcherUtils { } } - export async function createDirectoryIfNoExists() { - if (!(await checkFileExists(RNEDirectory))) { - await makeDirectoryAsync(RNEDirectory, { intermediates: true }); - } - } - - export async function checkFileExists(fileUri: string) { - const fileInfo = await getInfoAsync(fileUri); - return fileInfo.exists; - } - export function getFilenameFromUri(uri: string) { let cleanUri = uri.replace(/^https?:\/\//, ''); cleanUri = cleanUri.split('#')?.[0] ?? cleanUri; diff --git a/packages/react-native-executorch/third-party/include/absl/base/config.h b/packages/react-native-executorch/third-party/include/absl/base/config.h index d1bb2606d..64a30c743 100644 --- a/packages/react-native-executorch/third-party/include/absl/base/config.h +++ b/packages/react-native-executorch/third-party/include/absl/base/config.h @@ -976,7 +976,7 @@ static_assert(ABSL_INTERNAL_INLINE_NAMESPACE_STR[0] != 'h' || #error __EMSCRIPTEN_tiny__ is too big to fit in ABSL_INTERNAL_EMSCRIPTEN_VERSION #endif #define ABSL_INTERNAL_EMSCRIPTEN_VERSION \ - ((__EMSCRIPTEN_major__) * 1000000 + (__EMSCRIPTEN_minor__) * 1000 + \ + ((__EMSCRIPTEN_major__)*1000000 + (__EMSCRIPTEN_minor__)*1000 + \ (__EMSCRIPTEN_tiny__)) #endif #endif diff --git a/packages/react-native-executorch/third-party/include/absl/base/internal/raw_logging.h b/packages/react-native-executorch/third-party/include/absl/base/internal/raw_logging.h index 8abe8e0d4..140f58bd8 100644 --- a/packages/react-native-executorch/third-party/include/absl/base/internal/raw_logging.h +++ b/packages/react-native-executorch/third-party/include/absl/base/internal/raw_logging.h @@ -199,8 +199,8 @@ using InternalLogFunction = void (*)(absl::LogSeverity severity, const std::string &message); ABSL_INTERNAL_ATOMIC_HOOK_ATTRIBUTES - ABSL_DLL extern base_internal::AtomicHook - internal_log_function; +ABSL_DLL extern base_internal::AtomicHook + internal_log_function; // Registers hooks of the above types. Only a single hook of each type may be // registered. It is an error to call these functions multiple times with diff --git a/packages/react-native-executorch/third-party/include/absl/types/internal/optional.h b/packages/react-native-executorch/third-party/include/absl/types/internal/optional.h index c2dc8a258..6136db0f7 100644 --- a/packages/react-native-executorch/third-party/include/absl/types/internal/optional.h +++ b/packages/react-native-executorch/third-party/include/absl/types/internal/optional.h @@ -139,10 +139,9 @@ class optional_data_base : public optional_data_dtor_base { // Also, we should be checking is_trivially_copyable here, which is not // supported now, so we use is_trivially_* traits instead. template ::value && - absl::is_trivially_copy_assignable< - typename std::remove_cv::type>::value && - std::is_trivially_destructible::value> + bool unused = absl::is_trivially_copy_constructible::value + &&absl::is_trivially_copy_assignable::type>::value &&std::is_trivially_destructible::value> class optional_data; // Trivially copyable types @@ -184,8 +183,8 @@ class optional_data : public optional_data_base { } optional_data &operator=(optional_data &&rhs) noexcept( - std::is_nothrow_move_assignable::value && - std::is_nothrow_move_constructible::value) { + std::is_nothrow_move_assignable::value + &&std::is_nothrow_move_constructible::value) { if (rhs.engaged_) { this->assign(std::move(rhs.data_)); } else { diff --git a/packages/react-native-executorch/third-party/include/absl/types/optional.h b/packages/react-native-executorch/third-party/include/absl/types/optional.h index 4f4b19507..aeedec99f 100644 --- a/packages/react-native-executorch/third-party/include/absl/types/optional.h +++ b/packages/react-native-executorch/third-party/include/absl/types/optional.h @@ -388,8 +388,8 @@ class optional : private optional_internal::optional_data, // Swap, standard semantics void swap(optional &rhs) noexcept( - std::is_nothrow_move_constructible::value && - type_traits_internal::IsNothrowSwappable::value) { + std::is_nothrow_move_constructible::value + &&type_traits_internal::IsNothrowSwappable::value) { if (*this) { if (rhs) { type_traits_internal::Swap(**this, *rhs); diff --git a/packages/react-native-executorch/third-party/include/executorch/extension/llm/tokenizers/include/pytorch/tokenizers/pcre2_regex.h b/packages/react-native-executorch/third-party/include/executorch/extension/llm/tokenizers/include/pytorch/tokenizers/pcre2_regex.h index 87a4fb8a9..fee30475d 100644 --- a/packages/react-native-executorch/third-party/include/executorch/extension/llm/tokenizers/include/pytorch/tokenizers/pcre2_regex.h +++ b/packages/react-native-executorch/third-party/include/executorch/extension/llm/tokenizers/include/pytorch/tokenizers/pcre2_regex.h @@ -27,7 +27,7 @@ class Pcre2Regex : public IRegex { /** * @brief Construct a PCRE2 regex. */ - explicit Pcre2Regex() {}; + explicit Pcre2Regex(){}; /** * @brief Compile the given regex pattern. diff --git a/packages/react-native-executorch/third-party/include/nlohmann/json.hpp b/packages/react-native-executorch/third-party/include/nlohmann/json.hpp index 7c23b7bdf..feb0356c8 100644 --- a/packages/react-native-executorch/third-party/include/nlohmann/json.hpp +++ b/packages/react-native-executorch/third-party/include/nlohmann/json.hpp @@ -20135,8 +20135,8 @@ class // been invalidated; see https://github.com/nlohmann/json/issues/2962 #ifdef JSON_HEDLEY_MSVC_VERSION #pragma warning(push) -#pragma warning(disable \ - : 4127) // ignore warning to replace if with if constexpr +#pragma warning( \ + disable : 4127) // ignore warning to replace if with if constexpr #endif if (detail::is_ordered_map::value) { set_parents(); @@ -20261,9 +20261,10 @@ class case value_t::discarded: m_data.m_type = value_t::discarded; break; - default: // LCOV_EXCL_LINE - JSON_ASSERT(false); // NOLINT(cert-dcl03-c,hicpp-static-assert,misc-static-assert) - // LCOV_EXCL_LINE + default: // LCOV_EXCL_LINE + JSON_ASSERT( + false); // NOLINT(cert-dcl03-c,hicpp-static-assert,misc-static-assert) + // LCOV_EXCL_LINE } JSON_ASSERT(m_data.m_type == val.type()); set_parents(); @@ -20582,11 +20583,11 @@ class /// @brief copy assignment /// @sa https://json.nlohmann.me/api/basic_json/operator=/ basic_json &operator=(basic_json other) noexcept( - std::is_nothrow_move_constructible::value && - std::is_nothrow_move_assignable::value && - std::is_nothrow_move_constructible::value && - std::is_nothrow_move_assignable::value && - std::is_nothrow_move_assignable::value) { + std::is_nothrow_move_constructible< + value_t>::value &&std::is_nothrow_move_assignable::value + &&std::is_nothrow_move_constructible::value + &&std::is_nothrow_move_assignable::value + &&std::is_nothrow_move_assignable::value) { // check that passed value is valid other.assert_invariant(); @@ -21076,7 +21077,8 @@ class #if defined(JSON_HAS_CPP_14) constexpr #endif - auto get() const noexcept(noexcept( + auto + get() const noexcept(noexcept( std::declval().template get_impl( detail::priority_tag<4>{}))) -> decltype(std::declval() @@ -22785,11 +22787,11 @@ class /// @brief exchanges the values /// @sa https://json.nlohmann.me/api/basic_json/swap/ void swap(reference other) noexcept( - std::is_nothrow_move_constructible::value && - std::is_nothrow_move_assignable::value && - std::is_nothrow_move_constructible:: - value && // NOLINT(cppcoreguidelines-noexcept-swap,performance-noexcept-swap) - std::is_nothrow_move_assignable::value) { + std::is_nothrow_move_constructible< + value_t>::value &&std::is_nothrow_move_assignable::value + &&std::is_nothrow_move_constructible::value + && // NOLINT(cppcoreguidelines-noexcept-swap,performance-noexcept-swap) + std::is_nothrow_move_assignable::value) { std::swap(m_data.m_type, other.m_data.m_type); std::swap(m_data.m_value, other.m_data.m_value); @@ -22801,11 +22803,11 @@ class /// @brief exchanges the values /// @sa https://json.nlohmann.me/api/basic_json/swap/ friend void swap(reference left, reference right) noexcept( - std::is_nothrow_move_constructible::value && - std::is_nothrow_move_assignable::value && - std::is_nothrow_move_constructible:: - value && // NOLINT(cppcoreguidelines-noexcept-swap,performance-noexcept-swap) - std::is_nothrow_move_assignable::value) { + std::is_nothrow_move_constructible< + value_t>::value &&std::is_nothrow_move_assignable::value + &&std::is_nothrow_move_constructible::value + && // NOLINT(cppcoreguidelines-noexcept-swap,performance-noexcept-swap) + std::is_nothrow_move_assignable::value) { left.swap(right); } @@ -22984,8 +22986,9 @@ class // - any operand is discarded // in legacy mode, discarded values are considered ordered if // an operation is computed as an odd number of inverses of others - static bool compares_unordered(const_reference lhs, const_reference rhs, - bool inverse = false) noexcept { + static bool + compares_unordered(const_reference lhs, const_reference rhs, + bool inverse = false) noexcept { if ((lhs.is_number_float() && std::isnan(lhs.m_data.m_value.number_float) && rhs.is_number()) || (rhs.is_number_float() && std::isnan(rhs.m_data.m_value.number_float) && @@ -23025,8 +23028,8 @@ class /// @brief comparison: equal /// @sa https://json.nlohmann.me/api/basic_json/operator_eq/ template - requires std::is_scalar_v - bool operator==(ScalarType rhs) const noexcept { + requires std::is_scalar_v bool + operator==(ScalarType rhs) const noexcept { return *this == basic_json(rhs); } @@ -23080,8 +23083,8 @@ class /// @brief comparison: less than or equal /// @sa https://json.nlohmann.me/api/basic_json/operator_le/ template - requires std::is_scalar_v - bool operator<=(ScalarType rhs) const noexcept { + requires std::is_scalar_v bool + operator<=(ScalarType rhs) const noexcept { return *this <= basic_json(rhs); } @@ -23099,8 +23102,8 @@ class /// @brief comparison: greater than or equal /// @sa https://json.nlohmann.me/api/basic_json/operator_ge/ template - requires std::is_scalar_v - bool operator>=(ScalarType rhs) const noexcept { + requires std::is_scalar_v bool + operator>=(ScalarType rhs) const noexcept { return *this >= basic_json(rhs); } #endif @@ -23925,7 +23928,8 @@ class 3.11.0, basic_json::json_pointer or nlohmann::json_pointer< basic_json::string_t>) // NOLINT(readability/alt_tokens) - reference operator[](const ::nlohmann::json_pointer &ptr) { + reference + operator[](const ::nlohmann::json_pointer &ptr) { return ptr.get_unchecked(this); } @@ -24099,8 +24103,9 @@ class case value_t::binary: // LCOV_EXCL_LINE case value_t::discarded: // LCOV_EXCL_LINE default: // LCOV_EXCL_LINE - JSON_ASSERT(false); // NOLINT(cert-dcl03-c,hicpp-static-assert,misc-static-assert) - // LCOV_EXCL_LINE + JSON_ASSERT( + false); // NOLINT(cert-dcl03-c,hicpp-static-assert,misc-static-assert) + // LCOV_EXCL_LINE } }; @@ -24491,13 +24496,15 @@ struct less< /// @brief exchanges the values of two JSON objects /// @sa https://json.nlohmann.me/api/basic_json/std_swap/ NLOHMANN_BASIC_JSON_TPL_DECLARATION -inline void -swap(nlohmann::NLOHMANN_BASIC_JSON_TPL &j1, - nlohmann::NLOHMANN_BASIC_JSON_TPL &j2) noexcept( // NOLINT(readability-inconsistent-declaration-parameter-name, - // cert-dcl58-cpp) - is_nothrow_move_constructible:: - value && // NOLINT(misc-redundant-expression,cppcoreguidelines-noexcept-swap,performance-noexcept-swap) - is_nothrow_move_assignable::value) { +inline void swap( + nlohmann::NLOHMANN_BASIC_JSON_TPL &j1, + nlohmann::NLOHMANN_BASIC_JSON_TPL & + j2) noexcept( // NOLINT(readability-inconsistent-declaration-parameter-name, + // cert-dcl58-cpp) + is_nothrow_move_constructible::value + && // NOLINT(misc-redundant-expression,cppcoreguidelines-noexcept-swap,performance-noexcept-swap) + is_nothrow_move_assignable< + nlohmann::NLOHMANN_BASIC_JSON_TPL>::value) { j1.swap(j2); } diff --git a/readmes/README_fr.md b/readmes/README_fr.md index 4623bda5a..6fd2b3238 100644 --- a/readmes/README_fr.md +++ b/readmes/README_fr.md @@ -2,7 +2,6 @@ RNE Logo
-

React Native ExecuTorch

diff --git a/readmes/README_in.md b/readmes/README_in.md index e9433dde6..e0bcc42aa 100644 --- a/readmes/README_in.md +++ b/readmes/README_in.md @@ -25,7 +25,6 @@ README IN

- **React Native ExecuTorch** एक घोषणात्मक तरीका प्रदान करता है जिससे React Native का उपयोग करके उपकरण पर AI मॉडल्स को चलाया जा सके, जो **ExecuTorch** द्वारा संचालित है :rocket:. यह LLMs, कंप्यूटर विज़न मॉडल्स, और भी कई के लिए आउट-ऑफ़-द-बॉक्स सपोर्ट प्रदान करता है। इन मॉडलों का अन्वेषण करने के लिए हमारे [HuggingFace](https://huggingface.co/software-mansion) पेज पर जाएं। **ExecuTorch**, Meta द्वारा विकसित, एक नया फ्रेमवर्क है जो मोबाइल फोनों या माइक्रोकंट्रोलर्स जैसे उपकरणों पर AI मॉडल निष्पादन की अनुमति देता है। diff --git a/readmes/README_pt.md b/readmes/README_pt.md index d807bff77..80f579886 100644 --- a/readmes/README_pt.md +++ b/readmes/README_pt.md @@ -2,7 +2,6 @@ RNE Logo
-

React Native ExecuTorch

diff --git a/yarn.lock b/yarn.lock index ccc0d534e..436005c8d 100644 --- a/yarn.lock +++ b/yarn.lock @@ -60,7 +60,7 @@ __metadata: languageName: node linkType: hard -"@babel/core@npm:^7.11.6, @babel/core@npm:^7.12.3, @babel/core@npm:^7.13.16, @babel/core@npm:^7.20.0, @babel/core@npm:^7.21.3, @babel/core@npm:^7.23.9, @babel/core@npm:^7.25.2": +"@babel/core@npm:^7.11.6, @babel/core@npm:^7.12.3, @babel/core@npm:^7.20.0, @babel/core@npm:^7.21.3, @babel/core@npm:^7.23.9, @babel/core@npm:^7.25.2": version: 7.28.5 resolution: "@babel/core@npm:7.28.5" dependencies: @@ -332,7 +332,7 @@ __metadata: languageName: node linkType: hard -"@babel/parser@npm:^7.1.0, @babel/parser@npm:^7.13.16, @babel/parser@npm:^7.14.7, @babel/parser@npm:^7.20.7, @babel/parser@npm:^7.23.9, @babel/parser@npm:^7.25.3, @babel/parser@npm:^7.27.2, @babel/parser@npm:^7.28.5": +"@babel/parser@npm:^7.1.0, @babel/parser@npm:^7.14.7, @babel/parser@npm:^7.20.7, @babel/parser@npm:^7.23.9, @babel/parser@npm:^7.25.3, @babel/parser@npm:^7.27.2, @babel/parser@npm:^7.28.5": version: 7.28.5 resolution: "@babel/parser@npm:7.28.5" dependencies: @@ -416,7 +416,7 @@ __metadata: languageName: node linkType: hard -"@babel/plugin-proposal-class-properties@npm:^7.13.0, @babel/plugin-proposal-class-properties@npm:^7.18.0": +"@babel/plugin-proposal-class-properties@npm:^7.18.0": version: 7.18.6 resolution: "@babel/plugin-proposal-class-properties@npm:7.18.6" dependencies: @@ -452,7 +452,7 @@ __metadata: languageName: node linkType: hard -"@babel/plugin-proposal-nullish-coalescing-operator@npm:^7.13.8, @babel/plugin-proposal-nullish-coalescing-operator@npm:^7.18.0": +"@babel/plugin-proposal-nullish-coalescing-operator@npm:^7.18.0": version: 7.18.6 resolution: "@babel/plugin-proposal-nullish-coalescing-operator@npm:7.18.6" dependencies: @@ -503,7 +503,7 @@ __metadata: languageName: node linkType: hard -"@babel/plugin-proposal-optional-chaining@npm:^7.13.12, @babel/plugin-proposal-optional-chaining@npm:^7.20.0": +"@babel/plugin-proposal-optional-chaining@npm:^7.20.0": version: 7.21.0 resolution: "@babel/plugin-proposal-optional-chaining@npm:7.21.0" dependencies: @@ -982,7 +982,7 @@ __metadata: languageName: node linkType: hard -"@babel/plugin-transform-flow-strip-types@npm:^7.20.0, @babel/plugin-transform-flow-strip-types@npm:^7.25.2, @babel/plugin-transform-flow-strip-types@npm:^7.26.5, @babel/plugin-transform-flow-strip-types@npm:^7.27.1": +"@babel/plugin-transform-flow-strip-types@npm:^7.20.0, @babel/plugin-transform-flow-strip-types@npm:^7.25.2, @babel/plugin-transform-flow-strip-types@npm:^7.26.5": version: 7.27.1 resolution: "@babel/plugin-transform-flow-strip-types@npm:7.27.1" dependencies: @@ -1075,7 +1075,7 @@ __metadata: languageName: node linkType: hard -"@babel/plugin-transform-modules-commonjs@npm:^7.0.0, @babel/plugin-transform-modules-commonjs@npm:^7.13.8, @babel/plugin-transform-modules-commonjs@npm:^7.24.8, @babel/plugin-transform-modules-commonjs@npm:^7.27.1": +"@babel/plugin-transform-modules-commonjs@npm:^7.0.0, @babel/plugin-transform-modules-commonjs@npm:^7.24.8, @babel/plugin-transform-modules-commonjs@npm:^7.27.1": version: 7.27.1 resolution: "@babel/plugin-transform-modules-commonjs@npm:7.27.1" dependencies: @@ -1585,19 +1585,6 @@ __metadata: languageName: node linkType: hard -"@babel/preset-flow@npm:^7.13.13": - version: 7.27.1 - resolution: "@babel/preset-flow@npm:7.27.1" - dependencies: - "@babel/helper-plugin-utils": "npm:^7.27.1" - "@babel/helper-validator-option": "npm:^7.27.1" - "@babel/plugin-transform-flow-strip-types": "npm:^7.27.1" - peerDependencies: - "@babel/core": ^7.0.0-0 - checksum: 10/f3f25b390debf72a6ff0170a2d5198aea344ba96f05eaca0bae2c7072119706fd46321604d89646bda1842527cfc6eab8828a983ec90149218d2120b9cd26596 - languageName: node - linkType: hard - "@babel/preset-modules@npm:0.1.6-no-external-plugins": version: 0.1.6-no-external-plugins resolution: "@babel/preset-modules@npm:0.1.6-no-external-plugins" @@ -1627,7 +1614,7 @@ __metadata: languageName: node linkType: hard -"@babel/preset-typescript@npm:^7.13.0, @babel/preset-typescript@npm:^7.16.7, @babel/preset-typescript@npm:^7.23.0, @babel/preset-typescript@npm:^7.24.7": +"@babel/preset-typescript@npm:^7.16.7, @babel/preset-typescript@npm:^7.23.0, @babel/preset-typescript@npm:^7.24.7": version: 7.28.5 resolution: "@babel/preset-typescript@npm:7.28.5" dependencies: @@ -1642,21 +1629,6 @@ __metadata: languageName: node linkType: hard -"@babel/register@npm:^7.13.16": - version: 7.28.3 - resolution: "@babel/register@npm:7.28.3" - dependencies: - clone-deep: "npm:^4.0.1" - find-cache-dir: "npm:^2.0.0" - make-dir: "npm:^2.1.0" - pirates: "npm:^4.0.6" - source-map-support: "npm:^0.5.16" - peerDependencies: - "@babel/core": ^7.0.0-0 - checksum: 10/9475696152579933dbb0ffa7f47e0a3d130064101fc6ee471ec4cd8f20c70438798f3165708cb2ad29f585d81af0a26a4c233d732fbe63c7abec6a63b7d509d8 - languageName: node - linkType: hard - "@babel/runtime@npm:^7.20.0, @babel/runtime@npm:^7.25.0": version: 7.28.4 resolution: "@babel/runtime@npm:7.28.4" @@ -2272,6 +2244,19 @@ __metadata: languageName: node linkType: hard +"@dr.pogodin/react-native-fs@npm:^2.36.2": + version: 2.36.2 + resolution: "@dr.pogodin/react-native-fs@npm:2.36.2" + dependencies: + buffer: "npm:^6.0.3" + http-status-codes: "npm:^2.3.0" + peerDependencies: + react: "*" + react-native: "*" + checksum: 10/6a4b1c324386c455e01ccf45daf80474364079bc6495f0a65c1468e4e15607951754b81d9d720fb635a8caba5ba94b34719d0140357ce8e168172583e09156d4 + languageName: node + linkType: hard + "@egjs/hammerjs@npm:^2.0.17": version: 2.0.17 resolution: "@egjs/hammerjs@npm:2.0.17" @@ -2415,6 +2400,88 @@ __metadata: languageName: node linkType: hard +"@expo/cli@npm:54.0.22": + version: 54.0.22 + resolution: "@expo/cli@npm:54.0.22" + dependencies: + "@0no-co/graphql.web": "npm:^1.0.8" + "@expo/code-signing-certificates": "npm:^0.0.6" + "@expo/config": "npm:~12.0.13" + "@expo/config-plugins": "npm:~54.0.4" + "@expo/devcert": "npm:^1.2.1" + "@expo/env": "npm:~2.0.8" + "@expo/image-utils": "npm:^0.8.8" + "@expo/json-file": "npm:^10.0.8" + "@expo/metro": "npm:~54.2.0" + "@expo/metro-config": "npm:~54.0.14" + "@expo/osascript": "npm:^2.3.8" + "@expo/package-manager": "npm:^1.9.10" + "@expo/plist": "npm:^0.4.8" + "@expo/prebuild-config": "npm:^54.0.8" + "@expo/schema-utils": "npm:^0.1.8" + "@expo/spawn-async": "npm:^1.7.2" + "@expo/ws-tunnel": "npm:^1.0.1" + "@expo/xcpretty": "npm:^4.3.0" + "@react-native/dev-middleware": "npm:0.81.5" + "@urql/core": "npm:^5.0.6" + "@urql/exchange-retry": "npm:^1.3.0" + accepts: "npm:^1.3.8" + arg: "npm:^5.0.2" + better-opn: "npm:~3.0.2" + bplist-creator: "npm:0.1.0" + bplist-parser: "npm:^0.3.1" + chalk: "npm:^4.0.0" + ci-info: "npm:^3.3.0" + compression: "npm:^1.7.4" + connect: "npm:^3.7.0" + debug: "npm:^4.3.4" + env-editor: "npm:^0.4.1" + expo-server: "npm:^1.0.5" + freeport-async: "npm:^2.0.0" + getenv: "npm:^2.0.0" + glob: "npm:^13.0.0" + lan-network: "npm:^0.1.6" + minimatch: "npm:^9.0.0" + node-forge: "npm:^1.3.3" + npm-package-arg: "npm:^11.0.0" + ora: "npm:^3.4.0" + picomatch: "npm:^3.0.1" + pretty-bytes: "npm:^5.6.0" + pretty-format: "npm:^29.7.0" + progress: "npm:^2.0.3" + prompts: "npm:^2.3.2" + qrcode-terminal: "npm:0.11.0" + require-from-string: "npm:^2.0.2" + requireg: "npm:^0.2.2" + resolve: "npm:^1.22.2" + resolve-from: "npm:^5.0.0" + resolve.exports: "npm:^2.0.3" + semver: "npm:^7.6.0" + send: "npm:^0.19.0" + slugify: "npm:^1.3.4" + source-map-support: "npm:~0.5.21" + stacktrace-parser: "npm:^0.1.10" + structured-headers: "npm:^0.4.1" + tar: "npm:^7.5.2" + terminal-link: "npm:^2.1.1" + undici: "npm:^6.18.2" + wrap-ansi: "npm:^7.0.0" + ws: "npm:^8.12.1" + peerDependencies: + expo: "*" + expo-router: "*" + react-native: "*" + peerDependenciesMeta: + expo-router: + optional: true + react-native: + optional: true + bin: + expo-internal: build/bin/cli + checksum: 10/c20c89f5737e1f5b055567d6b560a920ea52ed17cab73fd5ab59d8f1640c62f7a9915d9d69eb0c5ebc85bd04505e3ac32ba329ab8f1e15da8a9fcd6c6dd8d808 + languageName: node + linkType: hard + "@expo/code-signing-certificates@npm:^0.0.5": version: 0.0.5 resolution: "@expo/code-signing-certificates@npm:0.0.5" @@ -2425,6 +2492,15 @@ __metadata: languageName: node linkType: hard +"@expo/code-signing-certificates@npm:^0.0.6": + version: 0.0.6 + resolution: "@expo/code-signing-certificates@npm:0.0.6" + dependencies: + node-forge: "npm:^1.3.3" + checksum: 10/4446cca45e8b48b90ba728e39aab6b1195ede730d7aba7d9830f635aa16a52634e6eba9dc510f83cc6ff6fb6b0e3077bc6021098f0157f6dba96f8494685c388 + languageName: node + linkType: hard + "@expo/config-plugins@npm:~54.0.3": version: 54.0.3 resolution: "@expo/config-plugins@npm:54.0.3" @@ -2447,6 +2523,35 @@ __metadata: languageName: node linkType: hard +"@expo/config-plugins@npm:~54.0.4": + version: 54.0.4 + resolution: "@expo/config-plugins@npm:54.0.4" + dependencies: + "@expo/config-types": "npm:^54.0.10" + "@expo/json-file": "npm:~10.0.8" + "@expo/plist": "npm:^0.4.8" + "@expo/sdk-runtime-versions": "npm:^1.0.0" + chalk: "npm:^4.1.2" + debug: "npm:^4.3.5" + getenv: "npm:^2.0.0" + glob: "npm:^13.0.0" + resolve-from: "npm:^5.0.0" + semver: "npm:^7.5.4" + slash: "npm:^3.0.0" + slugify: "npm:^1.6.6" + xcode: "npm:^3.0.1" + xml2js: "npm:0.6.0" + checksum: 10/55dab3f5f29b6dfb58bc32a9b0a681766f6b260ee94b1c295f67ac3c5e8f372afc512bb416f2e50901e387d4012e3a4a8fd3b461e5aa8c20e16fdcde64a07327 + languageName: node + linkType: hard + +"@expo/config-types@npm:^54.0.10": + version: 54.0.10 + resolution: "@expo/config-types@npm:54.0.10" + checksum: 10/7e4d598d2d1905dc53f2b30d5a1e0817dd486b13c89a24575deb4e25ec441b0de009d156f041a3c9a1f2121dfba28f2a24fd4fb5a056cac90502ca67c639bb8a + languageName: node + linkType: hard + "@expo/config-types@npm:^54.0.9": version: 54.0.9 resolution: "@expo/config-types@npm:54.0.9" @@ -2475,6 +2580,27 @@ __metadata: languageName: node linkType: hard +"@expo/config@npm:~12.0.13": + version: 12.0.13 + resolution: "@expo/config@npm:12.0.13" + dependencies: + "@babel/code-frame": "npm:~7.10.4" + "@expo/config-plugins": "npm:~54.0.4" + "@expo/config-types": "npm:^54.0.10" + "@expo/json-file": "npm:^10.0.8" + deepmerge: "npm:^4.3.1" + getenv: "npm:^2.0.0" + glob: "npm:^13.0.0" + require-from-string: "npm:^2.0.2" + resolve-from: "npm:^5.0.0" + resolve-workspace-root: "npm:^2.0.0" + semver: "npm:^7.6.0" + slugify: "npm:^1.3.4" + sucrase: "npm:~3.35.1" + checksum: 10/2caac758fb706a75fc6d07df31c24c22d633f522091148e615d9c28475ae35cfaed29458cfd08f13d40d71d33715e5ac618af78591c11886529157b8519fe4ea + languageName: node + linkType: hard + "@expo/devcert@npm:^1.2.1": version: 1.2.1 resolution: "@expo/devcert@npm:1.2.1" @@ -2564,6 +2690,16 @@ __metadata: languageName: node linkType: hard +"@expo/json-file@npm:^10.0.9, @expo/json-file@npm:~10.0.8": + version: 10.0.9 + resolution: "@expo/json-file@npm:10.0.9" + dependencies: + "@babel/code-frame": "npm:~7.10.4" + json5: "npm:^2.2.3" + checksum: 10/40c758fcdb37b35aa5ca43acd2c882763f073672ccaf53038bf94100a5c511ff90e0d1f900a4c49394ae968bdbabbedfc39f961b7d60a47abecb8ec55c6863da + languageName: node + linkType: hard + "@expo/metro-config@npm:54.0.10, @expo/metro-config@npm:~54.0.10": version: 54.0.10 resolution: "@expo/metro-config@npm:54.0.10" @@ -2598,6 +2734,40 @@ __metadata: languageName: node linkType: hard +"@expo/metro-config@npm:54.0.14, @expo/metro-config@npm:~54.0.14": + version: 54.0.14 + resolution: "@expo/metro-config@npm:54.0.14" + dependencies: + "@babel/code-frame": "npm:^7.20.0" + "@babel/core": "npm:^7.20.0" + "@babel/generator": "npm:^7.20.5" + "@expo/config": "npm:~12.0.13" + "@expo/env": "npm:~2.0.8" + "@expo/json-file": "npm:~10.0.8" + "@expo/metro": "npm:~54.2.0" + "@expo/spawn-async": "npm:^1.7.2" + browserslist: "npm:^4.25.0" + chalk: "npm:^4.1.0" + debug: "npm:^4.3.2" + dotenv: "npm:~16.4.5" + dotenv-expand: "npm:~11.0.6" + getenv: "npm:^2.0.0" + glob: "npm:^13.0.0" + hermes-parser: "npm:^0.29.1" + jsc-safe-url: "npm:^0.2.4" + lightningcss: "npm:^1.30.1" + minimatch: "npm:^9.0.0" + postcss: "npm:~8.4.32" + resolve-from: "npm:^5.0.0" + peerDependencies: + expo: "*" + peerDependenciesMeta: + expo: + optional: true + checksum: 10/c1a67c187fcd9f3dd43cd1b33a500644715768ab55939d5e2ff354311709ea5fed2bb3c103610b0ddac961d7ab2f94f7a1d1f25d033af98690ed6b9cec9ac787 + languageName: node + linkType: hard + "@expo/metro-runtime@npm:^6.1.2": version: 6.1.2 resolution: "@expo/metro-runtime@npm:6.1.2" @@ -2638,6 +2808,28 @@ __metadata: languageName: node linkType: hard +"@expo/metro@npm:~54.2.0": + version: 54.2.0 + resolution: "@expo/metro@npm:54.2.0" + dependencies: + metro: "npm:0.83.3" + metro-babel-transformer: "npm:0.83.3" + metro-cache: "npm:0.83.3" + metro-cache-key: "npm:0.83.3" + metro-config: "npm:0.83.3" + metro-core: "npm:0.83.3" + metro-file-map: "npm:0.83.3" + metro-minify-terser: "npm:0.83.3" + metro-resolver: "npm:0.83.3" + metro-runtime: "npm:0.83.3" + metro-source-map: "npm:0.83.3" + metro-symbolicate: "npm:0.83.3" + metro-transform-plugins: "npm:0.83.3" + metro-transform-worker: "npm:0.83.3" + checksum: 10/36087cec4cb1788f6c8f6148f9dcd30e8d3693fbf8a14f8b0a3c9575895bd6b1847690c958181d7e92718d49ab66df285a79d64ff3c13e4168bbfee26b670d7f + languageName: node + linkType: hard + "@expo/osascript@npm:^2.3.8": version: 2.3.8 resolution: "@expo/osascript@npm:2.3.8" @@ -2648,6 +2840,20 @@ __metadata: languageName: node linkType: hard +"@expo/package-manager@npm:^1.9.10": + version: 1.10.0 + resolution: "@expo/package-manager@npm:1.10.0" + dependencies: + "@expo/json-file": "npm:^10.0.9" + "@expo/spawn-async": "npm:^1.7.2" + chalk: "npm:^4.0.0" + npm-package-arg: "npm:^11.0.0" + ora: "npm:^3.4.0" + resolve-workspace-root: "npm:^2.0.0" + checksum: 10/61dc892764f886fc9bf4b957fbc8b686fc941f2ceb008428752070f61cd44a40bd4d48bef594b3169eacb389d9e44f86c6b0003da8ef1c8ab3948dfd88875328 + languageName: node + linkType: hard + "@expo/package-manager@npm:^1.9.9": version: 1.9.9 resolution: "@expo/package-manager@npm:1.9.9" @@ -2693,6 +2899,26 @@ __metadata: languageName: node linkType: hard +"@expo/prebuild-config@npm:^54.0.8": + version: 54.0.8 + resolution: "@expo/prebuild-config@npm:54.0.8" + dependencies: + "@expo/config": "npm:~12.0.13" + "@expo/config-plugins": "npm:~54.0.4" + "@expo/config-types": "npm:^54.0.10" + "@expo/image-utils": "npm:^0.8.8" + "@expo/json-file": "npm:^10.0.8" + "@react-native/normalize-colors": "npm:0.81.5" + debug: "npm:^4.3.1" + resolve-from: "npm:^5.0.0" + semver: "npm:^7.6.0" + xml2js: "npm:0.6.0" + peerDependencies: + expo: "*" + checksum: 10/67f0fd1ad9332ff10c554e4b31602656daf222f2c51cebde9c024cb47b7ea13653ee1b01a00b6ea7cdf8fe8c99e20955788de9dec578c394e6b2357ef5919ab9 + languageName: node + linkType: hard + "@expo/schema-utils@npm:^0.1.8": version: 0.1.8 resolution: "@expo/schema-utils@npm:0.1.8" @@ -3162,6 +3388,15 @@ __metadata: languageName: node linkType: hard +"@kesha-antonov/react-native-background-downloader@npm:^4.4.5": + version: 4.4.5 + resolution: "@kesha-antonov/react-native-background-downloader@npm:4.4.5" + peerDependencies: + react-native: ">=0.57.0" + checksum: 10/56c90b08f5efdbc017a373dd3358470b5879f1ccb28b9a25df6b725fb6cc534d7d60c989167e07774f4c8bca530b22ed383322f5f105f8bd58bac2d85914c548 + languageName: node + linkType: hard + "@nicolo-ribaudo/eslint-scope-5-internals@npm:5.1.1-v1": version: 5.1.1-v1 resolution: "@nicolo-ribaudo/eslint-scope-5-internals@npm:5.1.1-v1" @@ -3798,6 +4033,46 @@ __metadata: languageName: node linkType: hard +"@react-native-executorch/bare-resource-fetcher@workspace:packages/bare-resource-fetcher": + version: 0.0.0-use.local + resolution: "@react-native-executorch/bare-resource-fetcher@workspace:packages/bare-resource-fetcher" + dependencies: + "@dr.pogodin/react-native-fs": "npm:^2.36.2" + "@kesha-antonov/react-native-background-downloader": "npm:^4.4.5" + "@types/react": "npm:~19.1.10" + react: "npm:19.1.0" + react-native: "npm:0.81.5" + react-native-executorch: "workspace:*" + typescript: "npm:~5.9.2" + peerDependencies: + "@dr.pogodin/react-native-fs": ^2.0.0 + "@kesha-antonov/react-native-background-downloader": ^4.0.0 + react-native: "*" + react-native-executorch: "*" + languageName: unknown + linkType: soft + +"@react-native-executorch/expo-resource-fetcher@workspace:*, @react-native-executorch/expo-resource-fetcher@workspace:packages/expo-resource-fetcher": + version: 0.0.0-use.local + resolution: "@react-native-executorch/expo-resource-fetcher@workspace:packages/expo-resource-fetcher" + dependencies: + "@types/react": "npm:~19.1.10" + expo: "npm:^54.0.0" + expo-asset: "npm:12.0.11" + expo-file-system: "npm:^19.0.20" + react: "npm:19.1.0" + react-native: "npm:0.81.5" + react-native-executorch: "workspace:*" + typescript: "npm:~5.9.2" + peerDependencies: + expo: ">=54.0.0" + expo-asset: ^12.0.0 + expo-file-system: ^19.0.0 + react-native: "*" + react-native-executorch: "*" + languageName: unknown + linkType: soft + "@react-native/assets-registry@npm:0.81.5": version: 0.81.5 resolution: "@react-native/assets-registry@npm:0.81.5" @@ -3805,15 +4080,6 @@ __metadata: languageName: node linkType: hard -"@react-native/babel-plugin-codegen@npm:0.76.9": - version: 0.76.9 - resolution: "@react-native/babel-plugin-codegen@npm:0.76.9" - dependencies: - "@react-native/codegen": "npm:0.76.9" - checksum: 10/f70b341954c8a83de7c9ee0d261f55b326204d2c02ff7680e091a999fa9137b654aa8fe13769ab76daac5d12b47532833cf49b9bdc7a00011d260c8871b5b4cf - languageName: node - linkType: hard - "@react-native/babel-plugin-codegen@npm:0.81.5": version: 0.81.5 resolution: "@react-native/babel-plugin-codegen@npm:0.81.5" @@ -3824,61 +4090,6 @@ __metadata: languageName: node linkType: hard -"@react-native/babel-preset@npm:0.76.9": - version: 0.76.9 - resolution: "@react-native/babel-preset@npm:0.76.9" - dependencies: - "@babel/core": "npm:^7.25.2" - "@babel/plugin-proposal-export-default-from": "npm:^7.24.7" - "@babel/plugin-syntax-dynamic-import": "npm:^7.8.3" - "@babel/plugin-syntax-export-default-from": "npm:^7.24.7" - "@babel/plugin-syntax-nullish-coalescing-operator": "npm:^7.8.3" - "@babel/plugin-syntax-optional-chaining": "npm:^7.8.3" - "@babel/plugin-transform-arrow-functions": "npm:^7.24.7" - "@babel/plugin-transform-async-generator-functions": "npm:^7.25.4" - "@babel/plugin-transform-async-to-generator": "npm:^7.24.7" - "@babel/plugin-transform-block-scoping": "npm:^7.25.0" - "@babel/plugin-transform-class-properties": "npm:^7.25.4" - "@babel/plugin-transform-classes": "npm:^7.25.4" - "@babel/plugin-transform-computed-properties": "npm:^7.24.7" - "@babel/plugin-transform-destructuring": "npm:^7.24.8" - "@babel/plugin-transform-flow-strip-types": "npm:^7.25.2" - "@babel/plugin-transform-for-of": "npm:^7.24.7" - "@babel/plugin-transform-function-name": "npm:^7.25.1" - "@babel/plugin-transform-literals": "npm:^7.25.2" - "@babel/plugin-transform-logical-assignment-operators": "npm:^7.24.7" - "@babel/plugin-transform-modules-commonjs": "npm:^7.24.8" - "@babel/plugin-transform-named-capturing-groups-regex": "npm:^7.24.7" - "@babel/plugin-transform-nullish-coalescing-operator": "npm:^7.24.7" - "@babel/plugin-transform-numeric-separator": "npm:^7.24.7" - "@babel/plugin-transform-object-rest-spread": "npm:^7.24.7" - "@babel/plugin-transform-optional-catch-binding": "npm:^7.24.7" - "@babel/plugin-transform-optional-chaining": "npm:^7.24.8" - "@babel/plugin-transform-parameters": "npm:^7.24.7" - "@babel/plugin-transform-private-methods": "npm:^7.24.7" - "@babel/plugin-transform-private-property-in-object": "npm:^7.24.7" - "@babel/plugin-transform-react-display-name": "npm:^7.24.7" - "@babel/plugin-transform-react-jsx": "npm:^7.25.2" - "@babel/plugin-transform-react-jsx-self": "npm:^7.24.7" - "@babel/plugin-transform-react-jsx-source": "npm:^7.24.7" - "@babel/plugin-transform-regenerator": "npm:^7.24.7" - "@babel/plugin-transform-runtime": "npm:^7.24.7" - "@babel/plugin-transform-shorthand-properties": "npm:^7.24.7" - "@babel/plugin-transform-spread": "npm:^7.24.7" - "@babel/plugin-transform-sticky-regex": "npm:^7.24.7" - "@babel/plugin-transform-typescript": "npm:^7.25.2" - "@babel/plugin-transform-unicode-regex": "npm:^7.24.7" - "@babel/template": "npm:^7.25.0" - "@react-native/babel-plugin-codegen": "npm:0.76.9" - babel-plugin-syntax-hermes-parser: "npm:^0.25.1" - babel-plugin-transform-flow-enums: "npm:^0.0.2" - react-refresh: "npm:^0.14.0" - peerDependencies: - "@babel/core": "*" - checksum: 10/3f4810482ea40b0f48add41320e440daabcae1c62ab9c344d0d426b81b2196a6c9b02882b594cfeb039e398fc238980c35f73c4b0182bfd15298de0faed13f0f - languageName: node - linkType: hard - "@react-native/babel-preset@npm:0.81.5": version: 0.81.5 resolution: "@react-native/babel-preset@npm:0.81.5" @@ -3934,24 +4145,6 @@ __metadata: languageName: node linkType: hard -"@react-native/codegen@npm:0.76.9": - version: 0.76.9 - resolution: "@react-native/codegen@npm:0.76.9" - dependencies: - "@babel/parser": "npm:^7.25.3" - glob: "npm:^7.1.1" - hermes-parser: "npm:0.23.1" - invariant: "npm:^2.2.4" - jscodeshift: "npm:^0.14.0" - mkdirp: "npm:^0.5.1" - nullthrows: "npm:^1.1.1" - yargs: "npm:^17.6.2" - peerDependencies: - "@babel/preset-env": ^7.1.6 - checksum: 10/4a4c97f8d7569fb1917e2dad71b4be66558be5d47993d666d14e886e65f19c7b1ebfd5d2205d382e7c3724b2d58bcbc8e23e5a64cb2a281d8869e0419153bab5 - languageName: node - linkType: hard - "@react-native/codegen@npm:0.81.5": version: 0.81.5 resolution: "@react-native/codegen@npm:0.81.5" @@ -4055,13 +4248,6 @@ __metadata: languageName: node linkType: hard -"@react-native/js-polyfills@npm:0.76.9": - version: 0.76.9 - resolution: "@react-native/js-polyfills@npm:0.76.9" - checksum: 10/3e1b64b9143a5ad69d7d56537792b1adba4f3b94aaf04f8a67f5ff3b851fd85df8d1cd79c9247a1811072dff93958d9142c7d41f125e7806138a03d6da105b03 - languageName: node - linkType: hard - "@react-native/js-polyfills@npm:0.81.5": version: 0.81.5 resolution: "@react-native/js-polyfills@npm:0.81.5" @@ -4069,29 +4255,29 @@ __metadata: languageName: node linkType: hard -"@react-native/metro-babel-transformer@npm:0.76.9": - version: 0.76.9 - resolution: "@react-native/metro-babel-transformer@npm:0.76.9" +"@react-native/metro-babel-transformer@npm:0.81.5": + version: 0.81.5 + resolution: "@react-native/metro-babel-transformer@npm:0.81.5" dependencies: "@babel/core": "npm:^7.25.2" - "@react-native/babel-preset": "npm:0.76.9" - hermes-parser: "npm:0.23.1" + "@react-native/babel-preset": "npm:0.81.5" + hermes-parser: "npm:0.29.1" nullthrows: "npm:^1.1.1" peerDependencies: "@babel/core": "*" - checksum: 10/c9cd4100142b634ecc61981bde71cbd044e4f5fa5f459284ed141599d46be4d43d1520319265ee02a4d474a98de6cc7f60a2ec4597e2b1bc76579c8a11d3619e + checksum: 10/401cd5e396a0c04865164c8321c29c17b9cdfbfef5efdf771befb77f830fd28c0bafe116f6d51930e684372f37b4a47f143a404341780187ae9e9fab0da39af4 languageName: node linkType: hard -"@react-native/metro-config@npm:^0.76.3": - version: 0.76.9 - resolution: "@react-native/metro-config@npm:0.76.9" +"@react-native/metro-config@npm:^0.81.5": + version: 0.81.5 + resolution: "@react-native/metro-config@npm:0.81.5" dependencies: - "@react-native/js-polyfills": "npm:0.76.9" - "@react-native/metro-babel-transformer": "npm:0.76.9" - metro-config: "npm:^0.81.0" - metro-runtime: "npm:^0.81.0" - checksum: 10/c52dd64967e6ead75d735702def2e29767f56321d888eae48b683e65118852c567c066755fa0f18c554773a8a0cb44493b436f516bf2c96bb6625f86e7439fec + "@react-native/js-polyfills": "npm:0.81.5" + "@react-native/metro-babel-transformer": "npm:0.81.5" + metro-config: "npm:^0.83.1" + metro-runtime: "npm:^0.83.1" + checksum: 10/13af9cb8f743e8ae51fe0c77db4c61070ef31074b985911ad03b53ec79985f3ba261f1b0026bc62b1b070a3954c8928b73d2d956fc13bad6ece3699b3f5d7254 languageName: node linkType: hard @@ -5230,15 +5416,6 @@ __metadata: languageName: node linkType: hard -"ast-types@npm:0.15.2": - version: 0.15.2 - resolution: "ast-types@npm:0.15.2" - dependencies: - tslib: "npm:^2.0.1" - checksum: 10/81680bd5829cdec33524e9aa3434e23f3919c0c388927068a0ff2e8466f55b0f34eae53e0007b3668742910c289481ab4e1d486a5318f618ae2fc93b5e7e863b - languageName: node - linkType: hard - "astral-regex@npm:^1.0.0": version: 1.0.0 resolution: "astral-regex@npm:1.0.0" @@ -5272,16 +5449,7 @@ __metadata: resolution: "available-typed-arrays@npm:1.0.7" dependencies: possible-typed-array-names: "npm:^1.0.0" - checksum: 10/6c9da3a66caddd83c875010a1ca8ef11eac02ba15fb592dc9418b2b5e7b77b645fa7729380a92d9835c2f05f2ca1b6251f39b993e0feb3f1517c74fa1af02cab - languageName: node - linkType: hard - -"babel-core@npm:^7.0.0-bridge.0": - version: 7.0.0-bridge.0 - resolution: "babel-core@npm:7.0.0-bridge.0" - peerDependencies: - "@babel/core": ^7.0.0-0 - checksum: 10/2a1cb879019dffb08d17bec36e13c3a6d74c94773f41c1fd8b14de13f149cc34b705b0a1e07b42fcf35917b49d78db6ff0c5c3b00b202a5235013d517b5c6bbb + checksum: 10/6c9da3a66caddd83c875010a1ca8ef11eac02ba15fb592dc9418b2b5e7b77b645fa7729380a92d9835c2f05f2ca1b6251f39b993e0feb3f1517c74fa1af02cab languageName: node linkType: hard @@ -5388,15 +5556,6 @@ __metadata: languageName: node linkType: hard -"babel-plugin-syntax-hermes-parser@npm:^0.25.1": - version: 0.25.1 - resolution: "babel-plugin-syntax-hermes-parser@npm:0.25.1" - dependencies: - hermes-parser: "npm:0.25.1" - checksum: 10/dc80fafde1aed8e60cf86ecd2e9920e7f35ffe02b33bd4e772daaa786167bcf508aac3fc1aea425ff4c7a0be94d82528f3fe8619b7f41dac853264272d640c04 - languageName: node - linkType: hard - "babel-plugin-syntax-hermes-parser@npm:^0.28.0": version: 0.28.1 resolution: "babel-plugin-syntax-hermes-parser@npm:0.28.1" @@ -5440,6 +5599,45 @@ __metadata: languageName: node linkType: hard +"babel-preset-expo@npm:~54.0.10": + version: 54.0.10 + resolution: "babel-preset-expo@npm:54.0.10" + dependencies: + "@babel/helper-module-imports": "npm:^7.25.9" + "@babel/plugin-proposal-decorators": "npm:^7.12.9" + "@babel/plugin-proposal-export-default-from": "npm:^7.24.7" + "@babel/plugin-syntax-export-default-from": "npm:^7.24.7" + "@babel/plugin-transform-class-static-block": "npm:^7.27.1" + "@babel/plugin-transform-export-namespace-from": "npm:^7.25.9" + "@babel/plugin-transform-flow-strip-types": "npm:^7.25.2" + "@babel/plugin-transform-modules-commonjs": "npm:^7.24.8" + "@babel/plugin-transform-object-rest-spread": "npm:^7.24.7" + "@babel/plugin-transform-parameters": "npm:^7.24.7" + "@babel/plugin-transform-private-methods": "npm:^7.24.7" + "@babel/plugin-transform-private-property-in-object": "npm:^7.24.7" + "@babel/plugin-transform-runtime": "npm:^7.24.7" + "@babel/preset-react": "npm:^7.22.15" + "@babel/preset-typescript": "npm:^7.23.0" + "@react-native/babel-preset": "npm:0.81.5" + babel-plugin-react-compiler: "npm:^1.0.0" + babel-plugin-react-native-web: "npm:~0.21.0" + babel-plugin-syntax-hermes-parser: "npm:^0.29.1" + babel-plugin-transform-flow-enums: "npm:^0.0.2" + debug: "npm:^4.3.4" + resolve-from: "npm:^5.0.0" + peerDependencies: + "@babel/runtime": ^7.20.0 + expo: "*" + react-refresh: ">=0.14.0 <1.0.0" + peerDependenciesMeta: + "@babel/runtime": + optional: true + expo: + optional: true + checksum: 10/210493e87fb2566fbf774a2bf20a0cfd552eb83f7d3fb71aa4b576ebeed6d367a1d7eda64cec8d166859efde6594789946676bae0d26176a45e4be9fac2fd6a4 + languageName: node + linkType: hard + "babel-preset-expo@npm:~54.0.8": version: 54.0.8 resolution: "babel-preset-expo@npm:54.0.8" @@ -6007,17 +6205,6 @@ __metadata: languageName: node linkType: hard -"clone-deep@npm:^4.0.1": - version: 4.0.1 - resolution: "clone-deep@npm:4.0.1" - dependencies: - is-plain-object: "npm:^2.0.4" - kind-of: "npm:^6.0.2" - shallow-clone: "npm:^3.0.0" - checksum: 10/770f912fe4e6f21873c8e8fbb1e99134db3b93da32df271d00589ea4a29dbe83a9808a322c93f3bcaf8584b8b4fa6fc269fc8032efbaa6728e0c9886c74467d2 - languageName: node - linkType: hard - "clone@npm:^1.0.2": version: 1.0.4 resolution: "clone@npm:1.0.4" @@ -6165,13 +6352,6 @@ __metadata: languageName: node linkType: hard -"commondir@npm:^1.0.1": - version: 1.0.1 - resolution: "commondir@npm:1.0.1" - checksum: 10/4620bc4936a4ef12ce7dfcd272bb23a99f2ad68889a4e4ad766c9f8ad21af982511934d6f7050d4a8bde90011b1c15d56e61a1b4576d9913efbf697a20172d6c - languageName: node - linkType: hard - "compressible@npm:~2.0.18": version: 2.0.18 resolution: "compressible@npm:2.0.18" @@ -6201,7 +6381,8 @@ __metadata: resolution: "computer-vision@workspace:apps/computer-vision" dependencies: "@babel/core": "npm:^7.25.2" - "@react-native/metro-config": "npm:^0.76.3" + "@react-native-executorch/expo-resource-fetcher": "workspace:*" + "@react-native/metro-config": "npm:^0.81.5" "@react-navigation/drawer": "npm:^7.3.9" "@react-navigation/native": "npm:^7.1.6" "@shopify/react-native-skia": "npm:2.2.12" @@ -6213,7 +6394,7 @@ __metadata: expo-linking: "npm:~8.0.10" expo-router: "npm:~6.0.17" expo-status-bar: "npm:~3.0.9" - metro-config: "npm:^0.81.0" + metro-config: "npm:^0.81.5" react: "npm:19.1.0" react-native: "npm:0.81.5" react-native-device-info: "npm:^14.0.4" @@ -7506,7 +7687,7 @@ __metadata: languageName: node linkType: hard -"esprima@npm:^4.0.0, esprima@npm:^4.0.1, esprima@npm:~4.0.0": +"esprima@npm:^4.0.0, esprima@npm:^4.0.1": version: 4.0.1 resolution: "esprima@npm:4.0.1" bin: @@ -7644,6 +7825,20 @@ __metadata: languageName: node linkType: hard +"expo-asset@npm:~12.0.12": + version: 12.0.12 + resolution: "expo-asset@npm:12.0.12" + dependencies: + "@expo/image-utils": "npm:^0.8.8" + expo-constants: "npm:~18.0.12" + peerDependencies: + expo: "*" + react: "*" + react-native: "*" + checksum: 10/7034316d820837c92ac70274be56a8e59181f1513805f8a4c85e16f12e1dd75ac6d6ae0b231bd8a76adbb71be6163c05b31b1d437f15b14745c70cc1f255c8a1 + languageName: node + linkType: hard + "expo-brightness@npm:~14.0.8": version: 14.0.8 resolution: "expo-brightness@npm:14.0.8" @@ -7677,7 +7872,30 @@ __metadata: languageName: node linkType: hard -"expo-file-system@npm:^19.0.20, expo-file-system@npm:~19.0.20": +"expo-constants@npm:~18.0.12, expo-constants@npm:~18.0.13": + version: 18.0.13 + resolution: "expo-constants@npm:18.0.13" + dependencies: + "@expo/config": "npm:~12.0.13" + "@expo/env": "npm:~2.0.8" + peerDependencies: + expo: "*" + react-native: "*" + checksum: 10/f29c72b6f5798bd37550aafcc89c3f1a630c4910a5b69c1e19d03544f6ebf0cb65adf39db600ccbeb6e60545b2b231d244373ef3139e3c75991b380940065c6b + languageName: node + linkType: hard + +"expo-file-system@npm:^19.0.20, expo-file-system@npm:~19.0.21": + version: 19.0.21 + resolution: "expo-file-system@npm:19.0.21" + peerDependencies: + expo: "*" + react-native: "*" + checksum: 10/00a2f13f8139724016f8b811303dd4a4070a315f80ee9e1877bcfd00773b38caafe4f1d3d7d4a87777e4ff53ba645aae0b4430e875f9ee5f277b88372b507811 + languageName: node + linkType: hard + +"expo-file-system@npm:~19.0.20": version: 19.0.20 resolution: "expo-file-system@npm:19.0.20" peerDependencies: @@ -7700,6 +7918,19 @@ __metadata: languageName: node linkType: hard +"expo-font@npm:~14.0.11": + version: 14.0.11 + resolution: "expo-font@npm:14.0.11" + dependencies: + fontfaceobserver: "npm:^2.1.0" + peerDependencies: + expo: "*" + react: "*" + react-native: "*" + checksum: 10/80acffecdbd49a2ba1d7ecd8727f355bf47c39873d92f5959ff3bf7fd1de3e6ac10ebe2a77b8238287c3f2b7d033df40b562505fec370f82d9444400e19d7518 + languageName: node + linkType: hard + "expo-keep-awake@npm:~15.0.8": version: 15.0.8 resolution: "expo-keep-awake@npm:15.0.8" @@ -7738,6 +7969,21 @@ __metadata: languageName: node linkType: hard +"expo-modules-autolinking@npm:3.0.24": + version: 3.0.24 + resolution: "expo-modules-autolinking@npm:3.0.24" + dependencies: + "@expo/spawn-async": "npm:^1.7.2" + chalk: "npm:^4.1.0" + commander: "npm:^7.2.0" + require-from-string: "npm:^2.0.2" + resolve-from: "npm:^5.0.0" + bin: + expo-modules-autolinking: bin/expo-modules-autolinking.js + checksum: 10/e3b77d2fa84b77e53dca2ef608b48c4db196957c76ac7cc1aba4eb2cca44b5082a16f7af8a3549a342c7a1362f069a76fb9ebdab4be6b467e3791ad48387e15a + languageName: node + linkType: hard + "expo-modules-core@npm:3.0.28": version: 3.0.28 resolution: "expo-modules-core@npm:3.0.28" @@ -7750,6 +7996,18 @@ __metadata: languageName: node linkType: hard +"expo-modules-core@npm:3.0.29": + version: 3.0.29 + resolution: "expo-modules-core@npm:3.0.29" + dependencies: + invariant: "npm:^2.2.4" + peerDependencies: + react: "*" + react-native: "*" + checksum: 10/db23a1c7321db54f40f0bcb9c18e7239d798fb7fb5d8ceedf09879f7ff4d90a85e375851796008006441326ed61c00ba00950b06bc7ea74f6ba648a9dac9d053 + languageName: node + linkType: hard + "expo-router@npm:~6.0.17": version: 6.0.17 resolution: "expo-router@npm:6.0.17" @@ -7831,7 +8089,53 @@ __metadata: languageName: node linkType: hard -"expo@npm:^54.0.0, expo@npm:^54.0.27": +"expo@npm:^54.0.0": + version: 54.0.32 + resolution: "expo@npm:54.0.32" + dependencies: + "@babel/runtime": "npm:^7.20.0" + "@expo/cli": "npm:54.0.22" + "@expo/config": "npm:~12.0.13" + "@expo/config-plugins": "npm:~54.0.4" + "@expo/devtools": "npm:0.1.8" + "@expo/fingerprint": "npm:0.15.4" + "@expo/metro": "npm:~54.2.0" + "@expo/metro-config": "npm:54.0.14" + "@expo/vector-icons": "npm:^15.0.3" + "@ungap/structured-clone": "npm:^1.3.0" + babel-preset-expo: "npm:~54.0.10" + expo-asset: "npm:~12.0.12" + expo-constants: "npm:~18.0.13" + expo-file-system: "npm:~19.0.21" + expo-font: "npm:~14.0.11" + expo-keep-awake: "npm:~15.0.8" + expo-modules-autolinking: "npm:3.0.24" + expo-modules-core: "npm:3.0.29" + pretty-format: "npm:^29.7.0" + react-refresh: "npm:^0.14.2" + whatwg-url-without-unicode: "npm:8.0.0-3" + peerDependencies: + "@expo/dom-webview": "*" + "@expo/metro-runtime": "*" + react: "*" + react-native: "*" + react-native-webview: "*" + peerDependenciesMeta: + "@expo/dom-webview": + optional: true + "@expo/metro-runtime": + optional: true + react-native-webview: + optional: true + bin: + expo: bin/cli + expo-modules-autolinking: bin/autolinking + fingerprint: bin/fingerprint + checksum: 10/78c9b88b98bcf424c00ffdd399b77e6fbf34f2c26a3d420e731bfccd9cdfb54e95f27b539d071155c4144ae7157e2700f9d22d4b2816974b20f8fcf0675bbb05 + languageName: node + linkType: hard + +"expo@npm:^54.0.27": version: 54.0.27 resolution: "expo@npm:54.0.27" dependencies: @@ -8022,26 +8326,6 @@ __metadata: languageName: node linkType: hard -"find-cache-dir@npm:^2.0.0": - version: 2.1.0 - resolution: "find-cache-dir@npm:2.1.0" - dependencies: - commondir: "npm:^1.0.1" - make-dir: "npm:^2.0.0" - pkg-dir: "npm:^3.0.0" - checksum: 10/60ad475a6da9f257df4e81900f78986ab367d4f65d33cf802c5b91e969c28a8762f098693d7a571b6e4dd4c15166c2da32ae2d18b6766a18e2071079448fdce4 - languageName: node - linkType: hard - -"find-up@npm:^3.0.0": - version: 3.0.0 - resolution: "find-up@npm:3.0.0" - dependencies: - locate-path: "npm:^3.0.0" - checksum: 10/38eba3fe7a66e4bc7f0f5a1366dc25508b7cfc349f852640e3678d26ad9a6d7e2c43eff0a472287de4a9753ef58f066a0ea892a256fa3636ad51b3fe1e17fae9 - languageName: node - linkType: hard - "find-up@npm:^4.0.0, find-up@npm:^4.1.0": version: 4.1.0 resolution: "find-up@npm:4.1.0" @@ -8097,13 +8381,6 @@ __metadata: languageName: node linkType: hard -"flow-parser@npm:0.*": - version: 0.292.0 - resolution: "flow-parser@npm:0.292.0" - checksum: 10/d5f9de995cdf6035bff4086a590b93ebcf94d759d94738ec83e7a98716553009caaa998bf9c19a97e2ed297c6ebcc4322060f6cff32937b2bcdff07f1ca90545 - languageName: node - linkType: hard - "fontfaceobserver@npm:^2.1.0": version: 2.3.0 resolution: "fontfaceobserver@npm:2.3.0" @@ -8453,7 +8730,7 @@ __metadata: languageName: node linkType: hard -"graceful-fs@npm:^4.1.11, graceful-fs@npm:^4.1.3, graceful-fs@npm:^4.1.6, graceful-fs@npm:^4.2.0, graceful-fs@npm:^4.2.4, graceful-fs@npm:^4.2.6, graceful-fs@npm:^4.2.9": +"graceful-fs@npm:^4.1.3, graceful-fs@npm:^4.1.6, graceful-fs@npm:^4.2.0, graceful-fs@npm:^4.2.4, graceful-fs@npm:^4.2.6, graceful-fs@npm:^4.2.9": version: 4.2.11 resolution: "graceful-fs@npm:4.2.11" checksum: 10/bf152d0ed1dc159239db1ba1f74fdbc40cb02f626770dcd5815c427ce0688c2635a06ed69af364396da4636d0408fcf7d4afdf7881724c3307e46aff30ca49e2 @@ -8531,13 +8808,6 @@ __metadata: languageName: node linkType: hard -"hermes-estree@npm:0.23.1": - version: 0.23.1 - resolution: "hermes-estree@npm:0.23.1" - checksum: 10/b7ad78f53044d53ec1c77e93036c16e34f6f0985c895540876301e4791d4db08da828870977140f5cf1ae34532bbb9d9d013a0a1a4a5a0da05177225648d5295 - languageName: node - linkType: hard - "hermes-estree@npm:0.25.1": version: 0.25.1 resolution: "hermes-estree@npm:0.25.1" @@ -8566,15 +8836,6 @@ __metadata: languageName: node linkType: hard -"hermes-parser@npm:0.23.1": - version: 0.23.1 - resolution: "hermes-parser@npm:0.23.1" - dependencies: - hermes-estree: "npm:0.23.1" - checksum: 10/de88df4f23bd8dc2ffa89c8a317445320af8c7705a2aeeb05c4dd171f037a747982be153a0a237b1c9c7337b79bceaeb5052934cb8a25fe2e2473294a5343334 - languageName: node - linkType: hard - "hermes-parser@npm:0.25.1": version: 0.25.1 resolution: "hermes-parser@npm:0.25.1" @@ -8679,6 +8940,13 @@ __metadata: languageName: node linkType: hard +"http-status-codes@npm:^2.3.0": + version: 2.3.0 + resolution: "http-status-codes@npm:2.3.0" + checksum: 10/1b8a01940b5e14d3c5b2f842313f4531469b41ce4fa40ca3aae5c82a3101828db2cc9406bfb2d50a46e6d521d106577b6656c2b065c76125b99ee54b2cbbac09 + languageName: node + linkType: hard + "https-proxy-agent@npm:^7.0.1, https-proxy-agent@npm:^7.0.5": version: 7.0.6 resolution: "https-proxy-agent@npm:7.0.6" @@ -9139,15 +9407,6 @@ __metadata: languageName: node linkType: hard -"is-plain-object@npm:^2.0.4": - version: 2.0.4 - resolution: "is-plain-object@npm:2.0.4" - dependencies: - isobject: "npm:^3.0.1" - checksum: 10/2a401140cfd86cabe25214956ae2cfee6fbd8186809555cd0e84574f88de7b17abacb2e477a6a658fa54c6083ecbda1e6ae404c7720244cd198903848fca70ca - languageName: node - linkType: hard - "is-regex@npm:^1.2.1": version: 1.2.1 resolution: "is-regex@npm:1.2.1" @@ -9308,13 +9567,6 @@ __metadata: languageName: node linkType: hard -"isobject@npm:^3.0.1": - version: 3.0.1 - resolution: "isobject@npm:3.0.1" - checksum: 10/db85c4c970ce30693676487cca0e61da2ca34e8d4967c2e1309143ff910c207133a969f9e4ddb2dc6aba670aabce4e0e307146c310350b298e74a31f7d464703 - languageName: node - linkType: hard - "istanbul-lib-coverage@npm:^3.0.0, istanbul-lib-coverage@npm:^3.2.0": version: 3.2.2 resolution: "istanbul-lib-coverage@npm:3.2.2" @@ -9903,37 +10155,6 @@ __metadata: languageName: node linkType: hard -"jscodeshift@npm:^0.14.0": - version: 0.14.0 - resolution: "jscodeshift@npm:0.14.0" - dependencies: - "@babel/core": "npm:^7.13.16" - "@babel/parser": "npm:^7.13.16" - "@babel/plugin-proposal-class-properties": "npm:^7.13.0" - "@babel/plugin-proposal-nullish-coalescing-operator": "npm:^7.13.8" - "@babel/plugin-proposal-optional-chaining": "npm:^7.13.12" - "@babel/plugin-transform-modules-commonjs": "npm:^7.13.8" - "@babel/preset-flow": "npm:^7.13.13" - "@babel/preset-typescript": "npm:^7.13.0" - "@babel/register": "npm:^7.13.16" - babel-core: "npm:^7.0.0-bridge.0" - chalk: "npm:^4.1.2" - flow-parser: "npm:0.*" - graceful-fs: "npm:^4.2.4" - micromatch: "npm:^4.0.4" - neo-async: "npm:^2.5.0" - node-dir: "npm:^0.1.17" - recast: "npm:^0.21.0" - temp: "npm:^0.8.4" - write-file-atomic: "npm:^2.3.0" - peerDependencies: - "@babel/preset-env": ^7.1.6 - bin: - jscodeshift: bin/jscodeshift.js - checksum: 10/fc355dde2287c026a682e8b38df5d8d1ff5c9ca044dfd558f2b6d17bb28f9257063bd0e47690814612e572804caa5383733c9d8ca8bc18e70bcee43e0458df59 - languageName: node - linkType: hard - "jsesc@npm:^3.0.2, jsesc@npm:~3.1.0": version: 3.1.0 resolution: "jsesc@npm:3.1.0" @@ -10049,13 +10270,6 @@ __metadata: languageName: node linkType: hard -"kind-of@npm:^6.0.2": - version: 6.0.3 - resolution: "kind-of@npm:6.0.3" - checksum: 10/5873d303fb36aad875b7538798867da2ae5c9e328d67194b0162a3659a627d22f742fc9c4ae95cd1704132a24b00cae5041fc00c0f6ef937dc17080dc4dbb962 - languageName: node - linkType: hard - "kleur@npm:^3.0.3": version: 3.0.3 resolution: "kleur@npm:3.0.3" @@ -10257,7 +10471,8 @@ __metadata: resolution: "llm@workspace:apps/llm" dependencies: "@babel/core": "npm:^7.25.2" - "@react-native/metro-config": "npm:^0.76.3" + "@react-native-executorch/expo-resource-fetcher": "workspace:*" + "@react-native/metro-config": "npm:^0.81.5" "@react-navigation/drawer": "npm:^7.3.9" "@react-navigation/native": "npm:^7.1.6" "@types/react": "npm:~19.1.10" @@ -10269,7 +10484,7 @@ __metadata: expo-linking: "npm:~8.0.10" expo-router: "npm:~6.0.17" expo-status-bar: "npm:~3.0.9" - metro-config: "npm:^0.81.0" + metro-config: "npm:^0.81.5" react: "npm:19.1.0" react-native: "npm:0.81.5" react-native-audio-api: "npm:^0.8.2" @@ -10287,16 +10502,6 @@ __metadata: languageName: unknown linkType: soft -"locate-path@npm:^3.0.0": - version: 3.0.0 - resolution: "locate-path@npm:3.0.0" - dependencies: - p-locate: "npm:^3.0.0" - path-exists: "npm:^3.0.0" - checksum: 10/53db3996672f21f8b0bf2a2c645ae2c13ffdae1eeecfcd399a583bce8516c0b88dcb4222ca6efbbbeb6949df7e46860895be2c02e8d3219abd373ace3bfb4e11 - languageName: node - linkType: hard - "locate-path@npm:^5.0.0": version: 5.0.0 resolution: "locate-path@npm:5.0.0" @@ -10418,16 +10623,6 @@ __metadata: languageName: node linkType: hard -"make-dir@npm:^2.0.0, make-dir@npm:^2.1.0": - version: 2.1.0 - resolution: "make-dir@npm:2.1.0" - dependencies: - pify: "npm:^4.0.1" - semver: "npm:^5.6.0" - checksum: 10/043548886bfaf1820323c6a2997e6d2fa51ccc2586ac14e6f14634f7458b4db2daf15f8c310e2a0abd3e0cddc64df1890d8fc7263033602c47bb12cbfcf86aab - languageName: node - linkType: hard - "make-dir@npm:^4.0.0": version: 4.0.0 resolution: "make-dir@npm:4.0.0" @@ -10697,7 +10892,7 @@ __metadata: languageName: node linkType: hard -"metro-config@npm:0.81.5, metro-config@npm:^0.81.0": +"metro-config@npm:0.81.5, metro-config@npm:^0.81.5": version: 0.81.5 resolution: "metro-config@npm:0.81.5" dependencies: @@ -10935,7 +11130,7 @@ __metadata: languageName: node linkType: hard -"metro-runtime@npm:0.81.5, metro-runtime@npm:^0.81.0": +"metro-runtime@npm:0.81.5": version: 0.81.5 resolution: "metro-runtime@npm:0.81.5" dependencies: @@ -11642,7 +11837,7 @@ __metadata: languageName: node linkType: hard -"minimatch@npm:^3.0.2, minimatch@npm:^3.0.4, minimatch@npm:^3.0.5, minimatch@npm:^3.1.1, minimatch@npm:^3.1.2": +"minimatch@npm:^3.0.4, minimatch@npm:^3.0.5, minimatch@npm:^3.1.1, minimatch@npm:^3.1.2": version: 3.1.2 resolution: "minimatch@npm:3.1.2" dependencies: @@ -11660,7 +11855,7 @@ __metadata: languageName: node linkType: hard -"minimist@npm:^1.2.0, minimist@npm:^1.2.6": +"minimist@npm:^1.2.0": version: 1.2.8 resolution: "minimist@npm:1.2.8" checksum: 10/908491b6cc15a6c440ba5b22780a0ba89b9810e1aea684e253e43c4e3b8d56ec1dcdd7ea96dde119c29df59c936cde16062159eae4225c691e19c70b432b6e6f @@ -11743,17 +11938,6 @@ __metadata: languageName: node linkType: hard -"mkdirp@npm:^0.5.1": - version: 0.5.6 - resolution: "mkdirp@npm:0.5.6" - dependencies: - minimist: "npm:^1.2.6" - bin: - mkdirp: bin/cmd.js - checksum: 10/0c91b721bb12c3f9af4b77ebf73604baf350e64d80df91754dc509491ae93bf238581e59c7188360cec7cb62fc4100959245a42cfe01834efedc5e9d068376c2 - languageName: node - linkType: hard - "mkdirp@npm:^1.0.4": version: 1.0.4 resolution: "mkdirp@npm:1.0.4" @@ -11825,13 +12009,6 @@ __metadata: languageName: node linkType: hard -"neo-async@npm:^2.5.0": - version: 2.6.2 - resolution: "neo-async@npm:2.6.2" - checksum: 10/1a7948fea86f2b33ec766bc899c88796a51ba76a4afc9026764aedc6e7cde692a09067031e4a1bf6db4f978ccd99e7f5b6c03fe47ad9865c3d4f99050d67e002 - languageName: node - linkType: hard - "nested-error-stacks@npm:~2.0.1": version: 2.0.1 resolution: "nested-error-stacks@npm:2.0.1" @@ -11856,16 +12033,7 @@ __metadata: languageName: node linkType: hard -"node-dir@npm:^0.1.17": - version: 0.1.17 - resolution: "node-dir@npm:0.1.17" - dependencies: - minimatch: "npm:^3.0.2" - checksum: 10/281fdea12d9c080a7250e5b5afefa3ab39426d40753ec8126a2d1e67f189b8824723abfed74f5d8549c5d78352d8c489fe08d0b067d7684c87c07283d38374a5 - languageName: node - linkType: hard - -"node-forge@npm:^1.2.1, node-forge@npm:^1.3.1": +"node-forge@npm:^1.2.1, node-forge@npm:^1.3.1, node-forge@npm:^1.3.3": version: 1.3.3 resolution: "node-forge@npm:1.3.3" checksum: 10/f41c31b9296771a4b8c955d58417471712f54f324603a35f8e6cbac19d5e6eaaf5fd5fd14584dfedecbf46a05438ded6eee60a5f2f0822fc5061aaa073cfc75d @@ -12204,7 +12372,7 @@ __metadata: languageName: node linkType: hard -"p-limit@npm:^2.0.0, p-limit@npm:^2.2.0": +"p-limit@npm:^2.2.0": version: 2.3.0 resolution: "p-limit@npm:2.3.0" dependencies: @@ -12222,15 +12390,6 @@ __metadata: languageName: node linkType: hard -"p-locate@npm:^3.0.0": - version: 3.0.0 - resolution: "p-locate@npm:3.0.0" - dependencies: - p-limit: "npm:^2.0.0" - checksum: 10/83991734a9854a05fe9dbb29f707ea8a0599391f52daac32b86f08e21415e857ffa60f0e120bfe7ce0cc4faf9274a50239c7895fc0d0579d08411e513b83a4ae - languageName: node - linkType: hard - "p-locate@npm:^4.1.0": version: 4.1.0 resolution: "p-locate@npm:4.1.0" @@ -12356,13 +12515,6 @@ __metadata: languageName: node linkType: hard -"path-exists@npm:^3.0.0": - version: 3.0.0 - resolution: "path-exists@npm:3.0.0" - checksum: 10/96e92643aa34b4b28d0de1cd2eba52a1c5313a90c6542d03f62750d82480e20bfa62bc865d5cfc6165f5fcd5aeb0851043c40a39be5989646f223300021bae0a - languageName: node - linkType: hard - "path-exists@npm:^4.0.0": version: 4.0.0 resolution: "path-exists@npm:4.0.0" @@ -12446,29 +12598,13 @@ __metadata: languageName: node linkType: hard -"pify@npm:^4.0.1": - version: 4.0.1 - resolution: "pify@npm:4.0.1" - checksum: 10/8b97cbf9dc6d4c1320cc238a2db0fc67547f9dc77011729ff353faf34f1936ea1a4d7f3c63b2f4980b253be77bcc72ea1e9e76ee3fd53cce2aafb6a8854d07ec - languageName: node - linkType: hard - -"pirates@npm:^4.0.1, pirates@npm:^4.0.4, pirates@npm:^4.0.6": +"pirates@npm:^4.0.1, pirates@npm:^4.0.4": version: 4.0.7 resolution: "pirates@npm:4.0.7" checksum: 10/2427f371366081ae42feb58214f04805d6b41d6b84d74480ebcc9e0ddbd7105a139f7c653daeaf83ad8a1a77214cf07f64178e76de048128fec501eab3305a96 languageName: node linkType: hard -"pkg-dir@npm:^3.0.0": - version: 3.0.0 - resolution: "pkg-dir@npm:3.0.0" - dependencies: - find-up: "npm:^3.0.0" - checksum: 10/70c9476ffefc77552cc6b1880176b71ad70bfac4f367604b2b04efd19337309a4eec985e94823271c7c0e83946fa5aeb18cd360d15d10a5d7533e19344bfa808 - languageName: node - linkType: hard - "pkg-dir@npm:^4.2.0": version: 4.2.0 resolution: "pkg-dir@npm:4.2.0" @@ -12954,9 +13090,6 @@ __metadata: "@react-native-community/cli": "npm:latest" "@types/jest": "npm:^29.5.5" "@types/react": "npm:~19.1.10" - expo: "npm:^54.0.0" - expo-asset: "npm:12.0.11" - expo-file-system: "npm:^19.0.20" jest: "npm:^29.7.0" jsonrepair: "npm:^3.12.0" jsonschema: "npm:^1.5.0" @@ -12967,9 +13100,6 @@ __metadata: typescript: "npm:~5.9.2" zod: "npm:^3.25.0" peerDependencies: - expo: ">=54.0.0" - expo-asset: ^12.0.0 - expo-file-system: ^19.0.0 react: "*" react-native: "*" languageName: unknown @@ -13288,18 +13418,6 @@ __metadata: languageName: node linkType: hard -"recast@npm:^0.21.0": - version: 0.21.5 - resolution: "recast@npm:0.21.5" - dependencies: - ast-types: "npm:0.15.2" - esprima: "npm:~4.0.0" - source-map: "npm:~0.6.1" - tslib: "npm:^2.0.1" - checksum: 10/b41da2bcf7e705511db2f27d17420ace027de8dd167de9f19190d4988a1f80d112f60c095101ac2f145c8657ddde0c5133eb71df20504efaf3fd9d76ad07e15d - languageName: node - linkType: hard - "reflect.getprototypeof@npm:^1.0.6, reflect.getprototypeof@npm:^1.0.9": version: 1.0.10 resolution: "reflect.getprototypeof@npm:1.0.10" @@ -13585,17 +13703,6 @@ __metadata: languageName: node linkType: hard -"rimraf@npm:~2.6.2": - version: 2.6.3 - resolution: "rimraf@npm:2.6.3" - dependencies: - glob: "npm:^7.1.3" - bin: - rimraf: ./bin.js - checksum: 10/756419f2fa99aa119c46a9fc03e09d84ecf5421a80a72d1944c5088c9e4671e77128527a900a313ed9d3fdbdd37e2ae05486cd7e9116d5812d8c31f2399d7c86 - languageName: node - linkType: hard - "run-parallel@npm:^1.1.9": version: 1.2.0 resolution: "run-parallel@npm:1.2.0" @@ -13683,15 +13790,6 @@ __metadata: languageName: node linkType: hard -"semver@npm:^5.6.0": - version: 5.7.2 - resolution: "semver@npm:5.7.2" - bin: - semver: bin/semver - checksum: 10/fca14418a174d4b4ef1fecb32c5941e3412d52a4d3d85165924ce3a47fbc7073372c26faf7484ceb4bbc2bde25880c6b97e492473dc7e9708fdfb1c6a02d546e - languageName: node - linkType: hard - "semver@npm:^6.3.0, semver@npm:^6.3.1": version: 6.3.1 resolution: "semver@npm:6.3.1" @@ -13854,15 +13952,6 @@ __metadata: languageName: node linkType: hard -"shallow-clone@npm:^3.0.0": - version: 3.0.1 - resolution: "shallow-clone@npm:3.0.1" - dependencies: - kind-of: "npm:^6.0.2" - checksum: 10/e066bd540cfec5e1b0f78134853e0d892d1c8945fb9a926a579946052e7cb0c70ca4fc34f875a8083aa7910d751805d36ae64af250a6de6f3d28f9fa7be6c21b - languageName: node - linkType: hard - "shallowequal@npm:^1.1.0": version: 1.1.0 resolution: "shallowequal@npm:1.1.0" @@ -14062,7 +14151,7 @@ __metadata: languageName: node linkType: hard -"source-map-support@npm:^0.5.16, source-map-support@npm:~0.5.20, source-map-support@npm:~0.5.21": +"source-map-support@npm:~0.5.20, source-map-support@npm:~0.5.21": version: 0.5.21 resolution: "source-map-support@npm:0.5.21" dependencies: @@ -14079,7 +14168,7 @@ __metadata: languageName: node linkType: hard -"source-map@npm:^0.6.0, source-map@npm:^0.6.1, source-map@npm:~0.6.1": +"source-map@npm:^0.6.0, source-map@npm:^0.6.1": version: 0.6.1 resolution: "source-map@npm:0.6.1" checksum: 10/59ef7462f1c29d502b3057e822cdbdae0b0e565302c4dd1a95e11e793d8d9d62006cdc10e0fd99163ca33ff2071360cf50ee13f90440806e7ed57d81cba2f7ff @@ -14091,13 +14180,14 @@ __metadata: resolution: "speech@workspace:apps/speech" dependencies: "@babel/core": "npm:^7.25.2" - "@react-native/metro-config": "npm:^0.76.3" + "@react-native-executorch/expo-resource-fetcher": "workspace:*" + "@react-native/metro-config": "npm:^0.81.5" "@types/react": "npm:~19.1.10" buffer: "npm:^6.0.3" expo: "npm:^54.0.27" expo-font: "npm:~14.0.10" expo-status-bar: "npm:~3.0.9" - metro-config: "npm:^0.81.0" + metro-config: "npm:^0.81.5" react: "npm:19.1.0" react-native: "npm:0.81.5" react-native-audio-api: "npm:0.11.3" @@ -14505,15 +14595,6 @@ __metadata: languageName: node linkType: hard -"temp@npm:^0.8.4": - version: 0.8.4 - resolution: "temp@npm:0.8.4" - dependencies: - rimraf: "npm:~2.6.2" - checksum: 10/0a7f76b49637415bc391c3f6e69377cc4c38afac95132b4158fa711e77b70b082fe56fd886f9d11ffab9d148df181a105a93c8b618fb72266eeaa5e5ddbfe37f - languageName: node - linkType: hard - "terminal-link@npm:^2.1.1": version: 2.1.1 resolution: "terminal-link@npm:2.1.1" @@ -14554,6 +14635,7 @@ __metadata: resolution: "text-embeddings@workspace:apps/text-embeddings" dependencies: "@babel/core": "npm:^7.25.2" + "@react-native-executorch/expo-resource-fetcher": "workspace:*" "@react-navigation/drawer": "npm:^7.3.9" "@types/react": "npm:~19.1.10" expo: "npm:^54.0.27" @@ -14664,7 +14746,7 @@ __metadata: languageName: node linkType: hard -"tslib@npm:^2.0.0, tslib@npm:^2.0.1, tslib@npm:^2.0.3, tslib@npm:^2.1.0": +"tslib@npm:^2.0.0, tslib@npm:^2.0.3, tslib@npm:^2.1.0": version: 2.8.1 resolution: "tslib@npm:2.8.1" checksum: 10/3e2e043d5c2316461cb54e5c7fe02c30ef6dccb3384717ca22ae5c6b5bc95232a6241df19c622d9c73b809bea33b187f6dbc73030963e29950c2141bc32a79f7 @@ -15286,17 +15368,6 @@ __metadata: languageName: node linkType: hard -"write-file-atomic@npm:^2.3.0": - version: 2.4.3 - resolution: "write-file-atomic@npm:2.4.3" - dependencies: - graceful-fs: "npm:^4.1.11" - imurmurhash: "npm:^0.1.4" - signal-exit: "npm:^3.0.2" - checksum: 10/15ce863dce07075d0decedd7c9094f4461e46139d28a758c53162f24c0791c16cd2e7a76baa5b47b1a851fbb51e16f2fab739afb156929b22628f3225437135c - languageName: node - linkType: hard - "write-file-atomic@npm:^4.0.2": version: 4.0.2 resolution: "write-file-atomic@npm:4.0.2"