@strand-js/react-native 0.1.0 → 0.1.2

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/README.md ADDED
@@ -0,0 +1,35 @@
1
+ # @strand-js/react-native
2
+
3
+ React Native transport adapter for [Strand](https://github.com/strand-js/strand).
4
+
5
+ React Native's built-in `fetch` does not support streaming response bodies. This package patches it transparently so all Strand hooks work identically to web.
6
+
7
+ ## Install
8
+
9
+ ```bash
10
+ npm install @strand-js/react-native
11
+ ```
12
+
13
+ ## Usage
14
+
15
+ ```ts
16
+ // App.tsx — must be the first import
17
+ import '@strand-js/react-native'
18
+
19
+ // Everything else works as normal
20
+ import { StrandProvider } from '@strand-js/react'
21
+ ```
22
+
23
+ That's it. All hooks (`useConversation`, `useToolCall`, `useAgentSession`) work identically after this import.
24
+
25
+ ## Compatibility
26
+
27
+ | Platform | Status |
28
+ |---|---|
29
+ | React Native 0.73+ | ✅ |
30
+ | Expo SDK 50+ | ✅ |
31
+ | Expo Web | ✅ (no-op) |
32
+ | Node.js | ✅ (no-op) |
33
+ | Browsers | ✅ (no-op) |
34
+
35
+ [Full documentation →](https://github.com/strand-js/strand)
@@ -0,0 +1,4 @@
1
+ declare function createXHRStreamingFetch(): typeof fetch;
2
+ declare function applyStreamingFetchPatch(): void;
3
+
4
+ export { applyStreamingFetchPatch, createXHRStreamingFetch };
@@ -0,0 +1,4 @@
1
+ declare function createXHRStreamingFetch(): typeof fetch;
2
+ declare function applyStreamingFetchPatch(): void;
3
+
4
+ export { applyStreamingFetchPatch, createXHRStreamingFetch };
package/dist/index.js ADDED
@@ -0,0 +1,122 @@
1
+ "use strict";
2
+ var __defProp = Object.defineProperty;
3
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
4
+ var __getOwnPropNames = Object.getOwnPropertyNames;
5
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
6
+ var __export = (target, all) => {
7
+ for (var name in all)
8
+ __defProp(target, name, { get: all[name], enumerable: true });
9
+ };
10
+ var __copyProps = (to, from, except, desc) => {
11
+ if (from && typeof from === "object" || typeof from === "function") {
12
+ for (let key of __getOwnPropNames(from))
13
+ if (!__hasOwnProp.call(to, key) && key !== except)
14
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
15
+ }
16
+ return to;
17
+ };
18
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
19
+
20
+ // src/index.ts
21
+ var index_exports = {};
22
+ __export(index_exports, {
23
+ applyStreamingFetchPatch: () => applyStreamingFetchPatch,
24
+ createXHRStreamingFetch: () => createXHRStreamingFetch
25
+ });
26
+ module.exports = __toCommonJS(index_exports);
27
+
28
+ // src/patch.ts
29
+ var encoder = new TextEncoder();
30
+ function parseResponseHeaders(headersStr) {
31
+ const headers = {};
32
+ for (const line of headersStr.trim().split("\r\n")) {
33
+ const idx = line.indexOf(": ");
34
+ if (idx > 0) headers[line.slice(0, idx).toLowerCase()] = line.slice(idx + 2);
35
+ }
36
+ return headers;
37
+ }
38
+ function createXHRStreamingFetch() {
39
+ return (input, init) => {
40
+ const url = typeof input === "string" ? input : input instanceof URL ? input.href : input.url;
41
+ const method = init?.method ?? (typeof input !== "string" && !(input instanceof URL) ? input.method : "GET");
42
+ return new Promise((resolve, reject) => {
43
+ const xhr = new XMLHttpRequest();
44
+ xhr.open(method, url, true);
45
+ xhr.responseType = "text";
46
+ const reqHeaders = init?.headers ? new Headers(init.headers) : new Headers();
47
+ reqHeaders.forEach((value, key) => xhr.setRequestHeader(key, value));
48
+ let resolved = false;
49
+ let streamController = null;
50
+ let processedLength = 0;
51
+ const readableStream = new ReadableStream({
52
+ start(controller) {
53
+ streamController = controller;
54
+ },
55
+ cancel() {
56
+ xhr.abort();
57
+ }
58
+ });
59
+ function pushChunk() {
60
+ const text = xhr.responseText;
61
+ const newText = text.slice(processedLength);
62
+ processedLength = text.length;
63
+ if (newText && streamController) {
64
+ streamController.enqueue(encoder.encode(newText));
65
+ }
66
+ }
67
+ function resolveResponse() {
68
+ if (!resolved) {
69
+ resolved = true;
70
+ resolve(
71
+ new Response(readableStream, {
72
+ status: xhr.status,
73
+ statusText: xhr.statusText,
74
+ headers: parseResponseHeaders(xhr.getAllResponseHeaders())
75
+ })
76
+ );
77
+ }
78
+ }
79
+ xhr.onprogress = () => {
80
+ pushChunk();
81
+ resolveResponse();
82
+ };
83
+ xhr.onload = () => {
84
+ pushChunk();
85
+ streamController?.close();
86
+ resolveResponse();
87
+ };
88
+ xhr.onerror = () => {
89
+ const err = new TypeError("Network request failed");
90
+ streamController?.error(err);
91
+ if (!resolved) reject(err);
92
+ };
93
+ xhr.onabort = () => {
94
+ const err = new DOMException("Request aborted", "AbortError");
95
+ streamController?.error(err);
96
+ if (!resolved) reject(err);
97
+ };
98
+ if (init?.signal) {
99
+ init.signal.addEventListener("abort", () => xhr.abort());
100
+ }
101
+ xhr.send(init?.body ?? null);
102
+ });
103
+ };
104
+ }
105
+ var patched = false;
106
+ function applyStreamingFetchPatch() {
107
+ if (patched) return;
108
+ patched = true;
109
+ const nav = typeof navigator !== "undefined" ? navigator : null;
110
+ const isReactNative = nav?.product === "ReactNative";
111
+ if (isReactNative) {
112
+ globalThis.fetch = createXHRStreamingFetch();
113
+ }
114
+ }
115
+
116
+ // src/index.ts
117
+ applyStreamingFetchPatch();
118
+ // Annotate the CommonJS export names for ESM import in node:
119
+ 0 && (module.exports = {
120
+ applyStreamingFetchPatch,
121
+ createXHRStreamingFetch
122
+ });
package/dist/index.mjs ADDED
@@ -0,0 +1,94 @@
1
+ // src/patch.ts
2
+ var encoder = new TextEncoder();
3
+ function parseResponseHeaders(headersStr) {
4
+ const headers = {};
5
+ for (const line of headersStr.trim().split("\r\n")) {
6
+ const idx = line.indexOf(": ");
7
+ if (idx > 0) headers[line.slice(0, idx).toLowerCase()] = line.slice(idx + 2);
8
+ }
9
+ return headers;
10
+ }
11
+ function createXHRStreamingFetch() {
12
+ return (input, init) => {
13
+ const url = typeof input === "string" ? input : input instanceof URL ? input.href : input.url;
14
+ const method = init?.method ?? (typeof input !== "string" && !(input instanceof URL) ? input.method : "GET");
15
+ return new Promise((resolve, reject) => {
16
+ const xhr = new XMLHttpRequest();
17
+ xhr.open(method, url, true);
18
+ xhr.responseType = "text";
19
+ const reqHeaders = init?.headers ? new Headers(init.headers) : new Headers();
20
+ reqHeaders.forEach((value, key) => xhr.setRequestHeader(key, value));
21
+ let resolved = false;
22
+ let streamController = null;
23
+ let processedLength = 0;
24
+ const readableStream = new ReadableStream({
25
+ start(controller) {
26
+ streamController = controller;
27
+ },
28
+ cancel() {
29
+ xhr.abort();
30
+ }
31
+ });
32
+ function pushChunk() {
33
+ const text = xhr.responseText;
34
+ const newText = text.slice(processedLength);
35
+ processedLength = text.length;
36
+ if (newText && streamController) {
37
+ streamController.enqueue(encoder.encode(newText));
38
+ }
39
+ }
40
+ function resolveResponse() {
41
+ if (!resolved) {
42
+ resolved = true;
43
+ resolve(
44
+ new Response(readableStream, {
45
+ status: xhr.status,
46
+ statusText: xhr.statusText,
47
+ headers: parseResponseHeaders(xhr.getAllResponseHeaders())
48
+ })
49
+ );
50
+ }
51
+ }
52
+ xhr.onprogress = () => {
53
+ pushChunk();
54
+ resolveResponse();
55
+ };
56
+ xhr.onload = () => {
57
+ pushChunk();
58
+ streamController?.close();
59
+ resolveResponse();
60
+ };
61
+ xhr.onerror = () => {
62
+ const err = new TypeError("Network request failed");
63
+ streamController?.error(err);
64
+ if (!resolved) reject(err);
65
+ };
66
+ xhr.onabort = () => {
67
+ const err = new DOMException("Request aborted", "AbortError");
68
+ streamController?.error(err);
69
+ if (!resolved) reject(err);
70
+ };
71
+ if (init?.signal) {
72
+ init.signal.addEventListener("abort", () => xhr.abort());
73
+ }
74
+ xhr.send(init?.body ?? null);
75
+ });
76
+ };
77
+ }
78
+ var patched = false;
79
+ function applyStreamingFetchPatch() {
80
+ if (patched) return;
81
+ patched = true;
82
+ const nav = typeof navigator !== "undefined" ? navigator : null;
83
+ const isReactNative = nav?.product === "ReactNative";
84
+ if (isReactNative) {
85
+ globalThis.fetch = createXHRStreamingFetch();
86
+ }
87
+ }
88
+
89
+ // src/index.ts
90
+ applyStreamingFetchPatch();
91
+ export {
92
+ applyStreamingFetchPatch,
93
+ createXHRStreamingFetch
94
+ };
package/package.json CHANGED
@@ -1,6 +1,7 @@
1
1
  {
2
2
  "name": "@strand-js/react-native",
3
- "version": "0.1.0",
3
+ "version": "0.1.2",
4
+ "license": "MIT",
4
5
  "description": "React Native transport adapter for Strand — patches streaming fetch support",
5
6
  "main": "./dist/index.js",
6
7
  "module": "./dist/index.mjs",
@@ -15,8 +16,15 @@
15
16
  "files": [
16
17
  "dist"
17
18
  ],
19
+ "scripts": {
20
+ "build": "tsup src/index.ts --format esm,cjs --dts --clean",
21
+ "dev": "tsup src/index.ts --format esm,cjs --dts --watch",
22
+ "typecheck": "tsc --noEmit",
23
+ "test": "vitest run",
24
+ "lint": "eslint src"
25
+ },
18
26
  "dependencies": {
19
- "@strand-js/core": "0.1.0"
27
+ "@strand-js/core": "workspace:*"
20
28
  },
21
29
  "peerDependencies": {
22
30
  "react": "^18.0.0",
@@ -29,12 +37,5 @@
29
37
  },
30
38
  "publishConfig": {
31
39
  "access": "public"
32
- },
33
- "scripts": {
34
- "build": "tsup src/index.ts --format esm,cjs --dts --clean",
35
- "dev": "tsup src/index.ts --format esm,cjs --dts --watch",
36
- "typecheck": "tsc --noEmit",
37
- "test": "vitest run",
38
- "lint": "eslint src"
39
40
  }
40
- }
41
+ }