@automatalabs/react-native-transformers 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,251 @@
1
+ const { Directory, File, Paths } = require('expo-file-system');
2
+
3
+ const DEFAULT_MODEL_CACHE_PATH_SEGMENTS = Object.freeze([
4
+ 'automatalabs-react-native-transformers',
5
+ 'models',
6
+ ]);
7
+
8
+ function hashString(value) {
9
+ let hash = 0x811c9dc5;
10
+
11
+ for (let index = 0; index < value.length; index += 1) {
12
+ hash ^= value.charCodeAt(index);
13
+ hash = Math.imul(hash, 0x01000193);
14
+ }
15
+
16
+ return (hash >>> 0).toString(16).padStart(8, '0');
17
+ }
18
+
19
+ function sanitizeSegment(value) {
20
+ const normalized = encodeURIComponent(String(value))
21
+ .replace(/%/g, '_')
22
+ .replace(/[^A-Za-z0-9._-]+/g, '_')
23
+ .replace(/_+/g, '_')
24
+ .replace(/^_+|_+$/g, '');
25
+
26
+ return normalized.slice(0, 120) || 'index';
27
+ }
28
+
29
+ function getRequestPathSegments(request) {
30
+ const value = String(request ?? '');
31
+
32
+ try {
33
+ const url = new URL(value);
34
+ const segments = [sanitizeSegment(url.protocol.replace(/:$/, '')), sanitizeSegment(url.host)];
35
+ const pathnameSegments = url.pathname.split('/').filter(Boolean).map((segment) => sanitizeSegment(segment));
36
+
37
+ segments.push(...pathnameSegments);
38
+
39
+ if (segments.length === 2) {
40
+ segments.push('index');
41
+ }
42
+
43
+ if (url.search) {
44
+ segments[segments.length - 1] = `${segments[segments.length - 1]}__q_${hashString(url.search)}`;
45
+ }
46
+
47
+ return segments;
48
+ } catch {
49
+ const segments = value.split('/').filter(Boolean).map((segment) => sanitizeSegment(segment));
50
+ return segments.length > 0 ? segments : ['index'];
51
+ }
52
+ }
53
+
54
+ function ensureDirectory(directory) {
55
+ if (!directory.exists) {
56
+ directory.create({
57
+ intermediates: true,
58
+ idempotent: true,
59
+ });
60
+ }
61
+ }
62
+
63
+ function deleteIfExists(file) {
64
+ if (file?.exists) {
65
+ try {
66
+ file.delete();
67
+ } catch {
68
+ // ignore cleanup failures
69
+ }
70
+ }
71
+ }
72
+
73
+ function getCachePaths(rootDirectory, request) {
74
+ const requestPathSegments = getRequestPathSegments(request);
75
+ const directorySegments = requestPathSegments.slice(0, -1);
76
+ const leafSegment = requestPathSegments.at(-1) ?? 'index';
77
+ const requestHash = hashString(String(request ?? ''));
78
+ const baseName = `${leafSegment}__${requestHash}`;
79
+ const directory = new Directory(rootDirectory, ...directorySegments);
80
+
81
+ return {
82
+ directory,
83
+ dataFile: new File(directory, `${baseName}.data`),
84
+ metadataFile: new File(directory, `${baseName}.meta.json`),
85
+ tempDataFile: new File(directory, `${baseName}.tmp.data`),
86
+ tempMetadataFile: new File(directory, `${baseName}.tmp.meta.json`),
87
+ };
88
+ }
89
+
90
+ async function readMetadata(metadataFile) {
91
+ if (!metadataFile.exists) {
92
+ return null;
93
+ }
94
+
95
+ try {
96
+ return JSON.parse(await metadataFile.text());
97
+ } catch {
98
+ return null;
99
+ }
100
+ }
101
+
102
+ function createResponseHeaders(dataFile, metadata) {
103
+ const headers = new Headers(metadata?.headers ?? {});
104
+
105
+ if (!headers.has('content-length')) {
106
+ headers.set('content-length', String(dataFile.size ?? 0));
107
+ }
108
+
109
+ return headers;
110
+ }
111
+
112
+ async function writeMetadata(metadataFile, tempMetadataFile, metadata) {
113
+ deleteIfExists(tempMetadataFile);
114
+ tempMetadataFile.create({
115
+ intermediates: true,
116
+ overwrite: true,
117
+ });
118
+ tempMetadataFile.write(JSON.stringify(metadata));
119
+ deleteIfExists(metadataFile);
120
+ tempMetadataFile.move(metadataFile);
121
+ }
122
+
123
+ async function writeResponseToFile(file, response, progressCallback) {
124
+ deleteIfExists(file);
125
+ file.create({
126
+ intermediates: true,
127
+ overwrite: true,
128
+ });
129
+
130
+ const total = Number.parseInt(response.headers.get('content-length') ?? '0', 10) || 0;
131
+ let loaded = 0;
132
+
133
+ if (response.body?.getReader) {
134
+ const reader = response.body.getReader();
135
+ const handle = file.open();
136
+
137
+ try {
138
+ while (true) {
139
+ const { done, value } = await reader.read();
140
+
141
+ if (done) {
142
+ break;
143
+ }
144
+
145
+ if (value && value.length > 0) {
146
+ handle.writeBytes(value);
147
+ loaded += value.length;
148
+ progressCallback?.({
149
+ progress: total > 0 ? (loaded / total) * 100 : 0,
150
+ loaded,
151
+ total,
152
+ });
153
+ }
154
+ }
155
+ } finally {
156
+ handle.close();
157
+ }
158
+ } else {
159
+ const bytes = new Uint8Array(await response.arrayBuffer());
160
+ file.write(bytes);
161
+ loaded = bytes.length;
162
+ progressCallback?.({
163
+ progress: 100,
164
+ loaded,
165
+ total: total || loaded,
166
+ });
167
+ }
168
+ }
169
+
170
+ function normalizeCacheRootDirectory(directory) {
171
+ if (directory instanceof Directory) {
172
+ return directory;
173
+ }
174
+
175
+ if (typeof directory === 'string') {
176
+ return new Directory(directory);
177
+ }
178
+
179
+ return new Directory(Paths.cache, ...DEFAULT_MODEL_CACHE_PATH_SEGMENTS);
180
+ }
181
+
182
+ function getDefaultExpoFileSystemModelCacheDirectory() {
183
+ return normalizeCacheRootDirectory();
184
+ }
185
+
186
+ function createExpoFileSystemCache(options = {}) {
187
+ const rootDirectory = normalizeCacheRootDirectory(options.directory);
188
+ ensureDirectory(rootDirectory);
189
+
190
+ return {
191
+ async match(request) {
192
+ const { dataFile, metadataFile } = getCachePaths(rootDirectory, request);
193
+
194
+ if (!dataFile.exists) {
195
+ return undefined;
196
+ }
197
+
198
+ const metadata = await readMetadata(metadataFile);
199
+ return new Response(dataFile, {
200
+ headers: createResponseHeaders(dataFile, metadata),
201
+ status: metadata?.status ?? 200,
202
+ });
203
+ },
204
+
205
+ async put(request, response, progressCallback = undefined) {
206
+ const { directory, dataFile, metadataFile, tempDataFile, tempMetadataFile } = getCachePaths(
207
+ rootDirectory,
208
+ request,
209
+ );
210
+
211
+ ensureDirectory(directory);
212
+
213
+ try {
214
+ await writeResponseToFile(tempDataFile, response, progressCallback);
215
+ deleteIfExists(dataFile);
216
+ tempDataFile.move(dataFile);
217
+
218
+ await writeMetadata(metadataFile, tempMetadataFile, {
219
+ request: String(request ?? ''),
220
+ status: response.status ?? 200,
221
+ headers: Object.fromEntries(response.headers.entries()),
222
+ cachedAt: Date.now(),
223
+ });
224
+ } catch (error) {
225
+ deleteIfExists(tempDataFile);
226
+ deleteIfExists(tempMetadataFile);
227
+ throw error;
228
+ }
229
+ },
230
+
231
+ async delete(request) {
232
+ const { dataFile, metadataFile } = getCachePaths(rootDirectory, request);
233
+ const hadData = dataFile.exists;
234
+ const hadMetadata = metadataFile.exists;
235
+
236
+ deleteIfExists(dataFile);
237
+ deleteIfExists(metadataFile);
238
+
239
+ return hadData || hadMetadata;
240
+ },
241
+
242
+ get directory() {
243
+ return rootDirectory;
244
+ },
245
+ };
246
+ }
247
+
248
+ module.exports = {
249
+ createExpoFileSystemCache,
250
+ getDefaultExpoFileSystemModelCacheDirectory,
251
+ };
package/src/index.js ADDED
@@ -0,0 +1,27 @@
1
+ const {
2
+ installTransformersReactNativeGlobals,
3
+ configureTransformersEnvironment,
4
+ ensureTransformersFetch,
5
+ getSupportedExecutionProviderNames,
6
+ getDefaultExecutionProviders,
7
+ rewriteExecutionProviders,
8
+ sanitizeSessionOptions,
9
+ normalizeTransformersConfig,
10
+ normalizeTransformersOptions,
11
+ createExpoFileSystemCache,
12
+ getDefaultExpoFileSystemModelCacheDirectory,
13
+ } = require('./runtime');
14
+
15
+ module.exports = {
16
+ installTransformersReactNativeGlobals,
17
+ configureTransformersEnvironment,
18
+ ensureTransformersFetch,
19
+ getSupportedExecutionProviderNames,
20
+ getDefaultExecutionProviders,
21
+ rewriteExecutionProviders,
22
+ sanitizeSessionOptions,
23
+ normalizeTransformersConfig,
24
+ normalizeTransformersOptions,
25
+ createExpoFileSystemCache,
26
+ getDefaultExpoFileSystemModelCacheDirectory,
27
+ };
package/src/metro.js ADDED
@@ -0,0 +1,66 @@
1
+ const path = require('node:path');
2
+
3
+ function mergeUnique(existing = [], additions = []) {
4
+ return Array.from(new Set([...(existing ?? []), ...additions]));
5
+ }
6
+
7
+ function getTransformersReactNativeAliases(overrides = {}) {
8
+ const packageRoot = path.resolve(__dirname, '..');
9
+ const adapterPath = path.join(packageRoot, 'src/adapter/onnxruntime-web-webgpu.js');
10
+ const wrapperPath = path.join(packageRoot, 'src/transformers.js');
11
+ const transformersNodeEntryPath = require.resolve('@huggingface/transformers', {
12
+ paths: [process.cwd(), packageRoot],
13
+ });
14
+ const transformersWebEntryPath = path.join(path.dirname(transformersNodeEntryPath), 'transformers.web.js');
15
+ const onnxruntimePackagePath = require.resolve('onnxruntime-react-native/package.json', {
16
+ paths: [process.cwd(), packageRoot],
17
+ });
18
+ const onnxruntimeCommonPath = require.resolve('onnxruntime-common', {
19
+ paths: [process.cwd(), path.dirname(onnxruntimePackagePath), packageRoot],
20
+ });
21
+
22
+ return {
23
+ '@huggingface/transformers': wrapperPath,
24
+ '@automatalabs/react-native-transformers/internal-transformers-web': transformersWebEntryPath,
25
+ 'onnxruntime-node': adapterPath,
26
+ 'onnxruntime-web': adapterPath,
27
+ 'onnxruntime-web/webgpu': adapterPath,
28
+ 'onnxruntime-common': onnxruntimeCommonPath,
29
+ ...overrides,
30
+ };
31
+ }
32
+
33
+ function withTransformersReactNativeMetro(config, options = {}) {
34
+ const { aliases: aliasOverrides, watchFolders = [] } = options;
35
+ const aliases = getTransformersReactNativeAliases(aliasOverrides);
36
+ const previousResolveRequest = config?.resolver?.resolveRequest;
37
+
38
+ return {
39
+ ...config,
40
+ watchFolders: mergeUnique(config?.watchFolders, watchFolders),
41
+ resolver: {
42
+ ...config.resolver,
43
+ assetExts: mergeUnique(config?.resolver?.assetExts, ['onnx', 'ort']),
44
+ resolveRequest(context, moduleName, platform) {
45
+ const alias =
46
+ aliases[moduleName] ??
47
+ (moduleName.startsWith('onnxruntime-web/') ? aliases['onnxruntime-web'] : undefined);
48
+
49
+ if (alias) {
50
+ return context.resolveRequest(context, alias, platform);
51
+ }
52
+
53
+ if (previousResolveRequest) {
54
+ return previousResolveRequest(context, moduleName, platform);
55
+ }
56
+
57
+ return context.resolveRequest(context, moduleName, platform);
58
+ },
59
+ },
60
+ };
61
+ }
62
+
63
+ module.exports = {
64
+ getTransformersReactNativeAliases,
65
+ withTransformersReactNativeMetro,
66
+ };