@rn-org/react-native-thread 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.
package/LICENSE ADDED
@@ -0,0 +1,20 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 sriharsha-rently
4
+ Permission is hereby granted, free of charge, to any person obtaining a copy
5
+ of this software and associated documentation files (the "Software"), to deal
6
+ in the Software without restriction, including without limitation the rights
7
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
8
+ copies of the Software, and to permit persons to whom the Software is
9
+ furnished to do so, subject to the following conditions:
10
+
11
+ The above copyright notice and this permission notice shall be included in all
12
+ copies or substantial portions of the Software.
13
+
14
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
15
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
16
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
17
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
18
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
19
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
20
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,440 @@
1
+ # @rn-org/react-native-thread
2
+
3
+ Run JavaScript on real background threads in React Native — no Workers, no Worklets. Uses **JavaScriptCore** on iOS and **Mozilla Rhino** on Android, each on a dedicated OS-level thread. Built as a New Architecture **TurboModule**.
4
+
5
+ ---
6
+
7
+ ## Contents
8
+
9
+ - [Features](#features)
10
+ - [Requirements](#requirements)
11
+ - [Installation](#installation)
12
+ - [Babel plugin (required for Hermes)](#babel-plugin-required-for-hermes)
13
+ - [Quick start](#quick-start)
14
+ - [API reference](#api-reference)
15
+ - [runOnJS](#runonjs)
16
+ - [runOnNewJS](#runnewjs)
17
+ - [createThread](#createthread)
18
+ - [getThreads](#getthreads)
19
+ - [destroyThread](#destroythread)
20
+ - [onMessage](#onmessage)
21
+ - [ThreadHandle](#threadhandle)
22
+ - [ThreadInfo](#threadinfo)
23
+ - [ThreadTask](#threadtask)
24
+ - [Thread globals](#thread-globals)
25
+ - [postMessage](#postmessage)
26
+ - [console](#console)
27
+ - [Hermes & Babel plugin](#hermes--babel-plugin-deep-dive)
28
+ - [Constraints](#constraints)
29
+ - [Contributing](#contributing)
30
+ - [License](#license)
31
+
32
+ ---
33
+
34
+ ## Features
35
+
36
+ - **True background threads** — each thread runs its own JS engine on an OS-level thread; the main Hermes/JSC runtime is never blocked.
37
+ - **Unlimited threads** — create as many threads as you need; each is isolated.
38
+ - **Shared thread** (`runOnJS`) — fire-and-forget tasks on a single persistent background thread; no teardown required.
39
+ - **Per-thread message passing** — call `postMessage(data)` from any thread, receive it on the main thread with `onMessage` (callback or `await`).
40
+ - **Parameter injection** — pass a JSON-serialisable value from the main thread into the background function as a typed argument `(args) => { ... }`.
41
+ - **Named threads** — give threads friendly names; list or destroy them by name.
42
+ - **Full `console` support** — `console.log/info/warn/error/debug` work inside threads and appear in Logcat / Xcode logs.
43
+ - **Hermes-safe** — ships a Babel plugin that extracts function source at compile time so Hermes bytecode never breaks serialisation.
44
+ - **New Architecture only** — built on the TurboModule / Codegen pipeline.
45
+
46
+ ---
47
+
48
+ ## Requirements
49
+
50
+ | | Minimum |
51
+ |---|---|
52
+ | React Native | 0.76+ (New Architecture) |
53
+ | iOS | 15.1 |
54
+ | Android | API 24 |
55
+ | Node | 18+ |
56
+
57
+ ---
58
+
59
+ ## Installation
60
+
61
+ ```sh
62
+ npm install @rn-org/react-native-thread
63
+ # or
64
+ yarn add @rn-org/react-native-thread
65
+ ```
66
+
67
+ **iOS** — run pod install:
68
+
69
+ ```sh
70
+ cd ios && pod install
71
+ ```
72
+
73
+ **Android** — the Rhino dependency is declared in the library's `build.gradle`; no extra steps needed.
74
+
75
+ ---
76
+
77
+ ## Babel plugin (required for Hermes)
78
+
79
+ Hermes compiles your JS to bytecode at build time. That means `fn.toString()` at runtime returns a placeholder (`"function () { [bytecode] }"`) instead of source code. The library includes a Babel plugin that extracts arrow-function / function-expression source at **compile time** and replaces it with a string literal before Hermes ever sees it.
80
+
81
+ Add the plugin to your app's `babel.config.js`:
82
+
83
+ ```js
84
+ // babel.config.js
85
+ module.exports = {
86
+ presets: ['module:@react-native/babel-preset'],
87
+ plugins: [
88
+ '@rn-org/react-native-thread/babel-plugin',
89
+ // ... your other plugins
90
+ ],
91
+ };
92
+ ```
93
+
94
+ The plugin is a no-op on non-Hermes builds (JSC, V8, etc.).
95
+
96
+ ---
97
+
98
+ ## Quick start
99
+
100
+ ```tsx
101
+ import { runOnJS, createThread, getThreads, destroyThread } from '@rn-org/react-native-thread';
102
+
103
+ // ── 1. Fire and forget on the shared background thread ──────────────────────
104
+ runOnJS((args) => {
105
+ console.log('Limit is', args.limit);
106
+ }, { limit: 42 });
107
+
108
+ // ── 2. Create a named, persistent thread ────────────────────────────────────
109
+ const worker = createThread('MyWorker');
110
+
111
+ // Send work + params
112
+ worker.run(
113
+ (args) => {
114
+ var result = 0;
115
+ for (var i = 0; i < args.limit; i++) result += i;
116
+ postMessage({ sum: result });
117
+ },
118
+ { limit: 1_000_000 }
119
+ );
120
+
121
+ // Option A — callback
122
+ const unsubscribe = worker.onMessage((data) => {
123
+ console.log('Result from worker:', data);
124
+ });
125
+
126
+ // Option B — promise (resolves on the next message)
127
+ const data = await worker.onMessage();
128
+ console.log('Result from worker:', data);
129
+
130
+ // ── 3. List all running threads ─────────────────────────────────────────────
131
+ console.log(getThreads());
132
+ // [{ id: 1, name: 'RNOrgThread' }, { id: 2, name: 'MyWorker' }]
133
+
134
+ // ── 4. Clean up ─────────────────────────────────────────────────────────────
135
+ unsubscribe();
136
+ worker.destroy(); // by handle
137
+ destroyThread('MyWorker'); // by name — same effect
138
+ destroyThread(2); // by id — same effect
139
+ ```
140
+
141
+ ---
142
+
143
+ ## API reference
144
+
145
+ ### `runOnJS`
146
+
147
+ ```ts
148
+ function runOnJS(task: ThreadTask, params?: unknown): void
149
+ ```
150
+
151
+ Runs `task` on the **shared persistent background thread** (named `"RNOrgThread"`). The thread is created on first use and lives for the lifetime of the module. Ideal for fire-and-forget work where you don't need the overhead of managing a dedicated thread.
152
+
153
+ | Parameter | Type | Description |
154
+ |---|---|---|
155
+ | `task` | `ThreadTask` | Function or code string to execute. |
156
+ | `params` | `unknown` | Optional JSON-serialisable value passed as the first argument to the function. |
157
+
158
+ ---
159
+
160
+ ### `runOnNewJS`
161
+
162
+ ```ts
163
+ function runOnNewJS(task: ThreadTask, params?: unknown, name?: string): ThreadHandle
164
+ ```
165
+
166
+ Creates a **new isolated thread**, immediately runs `task` on it, and returns a `ThreadHandle`. The thread persists until you call `handle.destroy()` or `destroyThread(...)`.
167
+
168
+ | Parameter | Type | Description |
169
+ |---|---|---|
170
+ | `task` | `ThreadTask` | Function or code string to execute. |
171
+ | `params` | `unknown` | Optional value passed as the first argument to the function. |
172
+ | `name` | `string` | Optional display name. Default: `RNThread-<id>`. |
173
+
174
+ ---
175
+
176
+ ### `createThread`
177
+
178
+ ```ts
179
+ function createThread(name?: string): ThreadHandle
180
+ ```
181
+
182
+ Creates a **new persistent named thread** without immediately running any code. Use the returned `ThreadHandle` to dispatch work at any time.
183
+
184
+ | Parameter | Type | Description |
185
+ |---|---|---|
186
+ | `name` | `string` | Optional display name. Default: `RNThread-<id>`. |
187
+
188
+ ---
189
+
190
+ ### `getThreads`
191
+
192
+ ```ts
193
+ function getThreads(): ThreadInfo[]
194
+ ```
195
+
196
+ Returns a snapshot of every live thread currently managed by the library, including the `runOnJS` shared thread once it has been started. Threads destroyed via `destroyThread` or `handle.destroy()` are removed from this list immediately.
197
+
198
+ ```ts
199
+ const threads = getThreads();
200
+ // [
201
+ // { id: 1, name: 'RNOrgThread' },
202
+ // { id: 2, name: 'MyWorker' },
203
+ // ]
204
+ ```
205
+
206
+ ---
207
+
208
+ ### `destroyThread`
209
+
210
+ ```ts
211
+ function destroyThread(idOrName: number | string): void
212
+ ```
213
+
214
+ Destroys a thread and frees its resources. Accepts either the numeric thread ID or the thread's name. When a name is given, the first matching thread is destroyed.
215
+
216
+ This is the same function called by `ThreadHandle.destroy()`.
217
+
218
+ ```ts
219
+ destroyThread('MyWorker'); // by name
220
+ destroyThread(2); // by id
221
+ ```
222
+
223
+ In `__DEV__` mode a warning is logged if no matching thread is found.
224
+
225
+ ---
226
+
227
+ ### `onMessage`
228
+
229
+ ```ts
230
+ // Callback — fires on every message from any thread
231
+ function onMessage(
232
+ handler: (data: unknown, threadId: number) => void
233
+ ): () => void
234
+
235
+ // Promise — resolves once on the next message from any thread
236
+ function onMessage(): Promise<{ data: unknown; threadId: number }>
237
+ ```
238
+
239
+ Global listener that fires whenever **any** thread calls `postMessage(data)`. Prefer `ThreadHandle.onMessage` if you want messages scoped to a single thread.
240
+
241
+ ```ts
242
+ // Callback variant
243
+ const unsub = onMessage((data, threadId) => {
244
+ console.log(`Thread ${threadId} sent:`, data);
245
+ });
246
+ unsub(); // remove listener
247
+
248
+ // Promise variant — awaits the next message from any thread
249
+ const { data, threadId } = await onMessage();
250
+ console.log(`Thread ${threadId} sent:`, data);
251
+ ```
252
+
253
+ ---
254
+
255
+ ### `ThreadHandle`
256
+
257
+ Object returned by `createThread` and `runOnNewJS`.
258
+
259
+ ```ts
260
+ type ThreadHandle = {
261
+ readonly id: number;
262
+ readonly name: string;
263
+ run(task: ThreadTask, params?: unknown): void;
264
+ /** Callback variant — fires on every message. Returns unsubscribe fn. */
265
+ onMessage(handler: (data: unknown) => void): () => void;
266
+ /** Promise variant — resolves on the next message from this thread. */
267
+ onMessage(): Promise<unknown>;
268
+ destroy(): void;
269
+ };
270
+ ```
271
+
272
+ | Member | Description |
273
+ |---|---|
274
+ | `id` | Numeric ID assigned by the native layer. |
275
+ | `name` | Display name provided at creation (or the default `RNThread-<id>`). |
276
+ | `run(task, params?)` | Execute `task` on this thread. `params` is passed as the first argument to the function. Can be called multiple times. |
277
+ | `onMessage(handler)` | Subscribe to `postMessage` output from **this thread only**. Returns an unsubscribe function. |
278
+ | `onMessage()` | Returns a `Promise` that resolves with the next message from **this thread only**, then auto-unsubscribes. |
279
+ | `destroy()` | Shut down the thread and remove it from the registry. Equivalent to calling `destroyThread(handle.id)`. |
280
+
281
+ ---
282
+
283
+ ### `ThreadInfo`
284
+
285
+ ```ts
286
+ type ThreadInfo = {
287
+ readonly id: number;
288
+ readonly name: string;
289
+ };
290
+ ```
291
+
292
+ Returned by `getThreads()`.
293
+
294
+ ---
295
+
296
+ ### `ThreadTask`
297
+
298
+ ```ts
299
+ type ThreadTask = ((args?: any) => void) | string;
300
+ ```
301
+
302
+ Either an arrow function / function expression (transformed by the Babel plugin) or a raw JS code string. When a function is used, `params` is passed as the first argument (`args`).
303
+
304
+ ---
305
+
306
+ ## Thread globals
307
+
308
+ These globals are available inside every thread function:
309
+
310
+ ### `postMessage`
311
+
312
+ ```ts
313
+ declare function postMessage(data: unknown): void
314
+ ```
315
+
316
+ Sends `data` back to the main JS thread. The value is JSON-serialised in the background thread and JSON-parsed before reaching the `onMessage` handler. Must be JSON-serialisable (`object`, `array`, `string`, `number`, `boolean`, or `null`).
317
+
318
+ ```ts
319
+ worker.run((args) => {
320
+ postMessage({ status: 'done', value: args.multiply * 2 });
321
+ }, { multiply: 21 });
322
+
323
+ // Callback
324
+ worker.onMessage((data) => {
325
+ console.log(data); // { status: 'done', value: 42 }
326
+ });
327
+
328
+ // Promise
329
+ const data = await worker.onMessage();
330
+ console.log(data); // { status: 'done', value: 42 }
331
+ ```
332
+
333
+ ---
334
+
335
+ ### `console`
336
+
337
+ `console.log`, `.info`, `.warn`, `.error`, and `.debug` are all available and route to:
338
+
339
+ - **iOS** — `NSLog`, visible in Xcode / Console.app; tagged `[RNThread-<id>]`.
340
+ - **Android** — `android.util.Log`, visible in Logcat; tagged `RNThread-<id>`.
341
+
342
+ ---
343
+
344
+ ### `__params__`
345
+
346
+ ```ts
347
+ declare const __params__: any
348
+ ```
349
+
350
+ Injected by the library when you pass a second argument to `run()`, `runOnJS()`, or `runOnNewJS()`. The value is JSON-serialised on the main thread and prepended to the code string as:
351
+
352
+ ```js
353
+ var __params__ = <JSON>;
354
+ ```
355
+
356
+ Must be JSON-serialisable. If you access `__params__` without passing a value, it will be `undefined`.
357
+
358
+ ```ts
359
+ worker.run(
360
+ () => {
361
+ for (var i = 0; i < __params__.iterations; i++) {
362
+ // ...
363
+ }
364
+ postMessage('done');
365
+ },
366
+ { iterations: 50_000 }
367
+ );
368
+ ```
369
+
370
+ ---
371
+
372
+ ## Hermes & Babel plugin deep dive
373
+
374
+ ### The problem
375
+
376
+ The Hermes compiler converts your JS bundle to bytecode at build time. Any function whose `.toString()` is called at runtime returns something like:
377
+
378
+ ```
379
+ function () { [bytecode] }
380
+ ```
381
+
382
+ Because this library must *serialise* functions to strings and send them to a separate JS engine, `.toString()` alone doesn't work under Hermes.
383
+
384
+ ### The solution
385
+
386
+ The included Babel plugin runs at **compile time** — before Hermes touches the code — and replaces arrow-function / function-expression arguments to the thread API with string literals containing the original source wrapped as an IIFE:
387
+
388
+ ```js
389
+ // Input (your source)
390
+ worker.run((args) => {
391
+ postMessage(args.greeting);
392
+ }, { greeting: 'hello' });
393
+
394
+ // Output (what Hermes compiles)
395
+ worker.run("((args) => {\n postMessage(args.greeting);\n})({\"greeting\":\"hello\"})");
396
+ ```
397
+
398
+ The second `params` argument is left untouched; only the first (function) argument is transformed.
399
+
400
+ ### Supported call sites
401
+
402
+ | Pattern | Transformed |
403
+ |---|---|
404
+ | `runOnJS(() => { ... })` | Yes |
405
+ | `runOnNewJS(() => { ... })` | Yes |
406
+ | `anyHandle.run(() => { ... })` | Yes |
407
+ | Raw code string `run("...")` | No (already a string) |
408
+
409
+ ### Limitations
410
+
411
+ - Thread functions are **self-contained**: they run in an isolated JS engine with no access to the outer closure, imported modules, or the React tree.
412
+ - All values must be passed explicitly via the `args` parameter or `postMessage`.
413
+ - Thread function bodies must be **ASCII-safe**: Rhino (Android) does not support non-ASCII identifier characters in source mode.
414
+ - The `params` value must be **JSON-serialisable**: functions, `undefined`, `Map`, `Set`, etc. are not supported.
415
+
416
+ ---
417
+
418
+ ## Constraints summary
419
+
420
+ | Constraint | Reason |
421
+ |---|---|
422
+ | Functions are closure-isolated | They run in a completely separate JS engine |
423
+ | `params` must be JSON-serialisable | Serialised and passed as a function argument |
424
+ | `postMessage` payload must be JSON-serialisable | Transported as a JSON string over the bridge |
425
+ | Function body must be ASCII-safe | Rhino parser limitation |
426
+ | New Architecture required | TurboModule / Codegen only; no bridge fallback |
427
+
428
+ ---
429
+
430
+ ## Contributing
431
+
432
+ - [Development workflow](CONTRIBUTING.md#development-workflow)
433
+ - [Sending a pull request](CONTRIBUTING.md#sending-a-pull-request)
434
+ - [Code of conduct](CODE_OF_CONDUCT.md)
435
+
436
+ ---
437
+
438
+ ## License
439
+
440
+ MIT
@@ -0,0 +1,20 @@
1
+ require "json"
2
+
3
+ package = JSON.parse(File.read(File.join(__dir__, "package.json")))
4
+
5
+ Pod::Spec.new do |s|
6
+ s.name = "ReactNativeThread"
7
+ s.version = package["version"]
8
+ s.summary = package["description"]
9
+ s.homepage = package["homepage"]
10
+ s.license = package["license"]
11
+ s.authors = package["author"]
12
+
13
+ s.platforms = { :ios => min_ios_version_supported }
14
+ s.source = { :git => "https://github.com/sriharsha-rently/rn-org-react-native-thread.git", :tag => "#{s.version}" }
15
+
16
+ s.source_files = "ios/**/*.{h,m,mm,swift,cpp}"
17
+ s.private_header_files = "ios/**/*.h"
18
+
19
+ install_modules_dependencies(s)
20
+ end
@@ -0,0 +1,69 @@
1
+ buildscript {
2
+ ext.ReactNativeThread = [
3
+ kotlinVersion: "2.0.21",
4
+ minSdkVersion: 24,
5
+ compileSdkVersion: 36,
6
+ targetSdkVersion: 36
7
+ ]
8
+
9
+ ext.getExtOrDefault = { prop ->
10
+ if (rootProject.ext.has(prop)) {
11
+ return rootProject.ext.get(prop)
12
+ }
13
+
14
+ return ReactNativeThread[prop]
15
+ }
16
+
17
+ repositories {
18
+ google()
19
+ mavenCentral()
20
+ }
21
+
22
+ dependencies {
23
+ classpath "com.android.tools.build:gradle:8.7.2"
24
+ // noinspection DifferentKotlinGradleVersion
25
+ classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:${getExtOrDefault('kotlinVersion')}"
26
+ }
27
+ }
28
+
29
+
30
+ apply plugin: "com.android.library"
31
+ apply plugin: "kotlin-android"
32
+
33
+ apply plugin: "com.facebook.react"
34
+
35
+ android {
36
+ namespace "com.rnorg.reactnativethread"
37
+
38
+ compileSdkVersion getExtOrDefault("compileSdkVersion")
39
+
40
+ defaultConfig {
41
+ minSdkVersion getExtOrDefault("minSdkVersion")
42
+ targetSdkVersion getExtOrDefault("targetSdkVersion")
43
+ }
44
+
45
+ buildFeatures {
46
+ buildConfig true
47
+ }
48
+
49
+ buildTypes {
50
+ release {
51
+ minifyEnabled false
52
+ }
53
+ }
54
+
55
+ lint {
56
+ disable "GradleCompatible"
57
+ }
58
+
59
+ compileOptions {
60
+ sourceCompatibility JavaVersion.VERSION_1_8
61
+ targetCompatibility JavaVersion.VERSION_1_8
62
+ }
63
+ }
64
+
65
+ dependencies {
66
+ implementation "com.facebook.react:react-android"
67
+ // Rhino JS engine – provides a self-contained JS runtime for background threads
68
+ implementation "org.mozilla:rhino:1.7.15"
69
+ }
@@ -0,0 +1,2 @@
1
+ <manifest xmlns:android="http://schemas.android.com/apk/res/android">
2
+ </manifest>