@ivogt/rsc-router 0.0.0-experimental.1
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 +19 -0
- package/package.json +131 -0
- package/src/__mocks__/version.ts +6 -0
- package/src/__tests__/route-definition.test.ts +63 -0
- package/src/browser/event-controller.ts +876 -0
- package/src/browser/index.ts +18 -0
- package/src/browser/link-interceptor.ts +121 -0
- package/src/browser/lru-cache.ts +69 -0
- package/src/browser/merge-segment-loaders.ts +126 -0
- package/src/browser/navigation-bridge.ts +891 -0
- package/src/browser/navigation-client.ts +155 -0
- package/src/browser/navigation-store.ts +823 -0
- package/src/browser/partial-update.ts +545 -0
- package/src/browser/react/Link.tsx +248 -0
- package/src/browser/react/NavigationProvider.tsx +228 -0
- package/src/browser/react/ScrollRestoration.tsx +94 -0
- package/src/browser/react/context.ts +53 -0
- package/src/browser/react/index.ts +52 -0
- package/src/browser/react/location-state-shared.ts +120 -0
- package/src/browser/react/location-state.ts +62 -0
- package/src/browser/react/use-action.ts +240 -0
- package/src/browser/react/use-client-cache.ts +56 -0
- package/src/browser/react/use-handle.ts +178 -0
- package/src/browser/react/use-link-status.ts +134 -0
- package/src/browser/react/use-navigation.ts +150 -0
- package/src/browser/react/use-segments.ts +188 -0
- package/src/browser/request-controller.ts +149 -0
- package/src/browser/rsc-router.tsx +310 -0
- package/src/browser/scroll-restoration.ts +324 -0
- package/src/browser/server-action-bridge.ts +747 -0
- package/src/browser/shallow.ts +35 -0
- package/src/browser/types.ts +443 -0
- package/src/cache/__tests__/memory-segment-store.test.ts +487 -0
- package/src/cache/__tests__/memory-store.test.ts +484 -0
- package/src/cache/cache-scope.ts +565 -0
- package/src/cache/cf/__tests__/cf-cache-store.test.ts +361 -0
- package/src/cache/cf/cf-cache-store.ts +274 -0
- package/src/cache/cf/index.ts +19 -0
- package/src/cache/index.ts +52 -0
- package/src/cache/memory-segment-store.ts +150 -0
- package/src/cache/memory-store.ts +253 -0
- package/src/cache/types.ts +366 -0
- package/src/client.rsc.tsx +88 -0
- package/src/client.tsx +609 -0
- package/src/components/DefaultDocument.tsx +20 -0
- package/src/default-error-boundary.tsx +88 -0
- package/src/deps/browser.ts +8 -0
- package/src/deps/html-stream-client.ts +2 -0
- package/src/deps/html-stream-server.ts +2 -0
- package/src/deps/rsc.ts +10 -0
- package/src/deps/ssr.ts +2 -0
- package/src/errors.ts +259 -0
- package/src/handle.ts +120 -0
- package/src/handles/MetaTags.tsx +178 -0
- package/src/handles/index.ts +6 -0
- package/src/handles/meta.ts +247 -0
- package/src/href-client.ts +128 -0
- package/src/href.ts +139 -0
- package/src/index.rsc.ts +69 -0
- package/src/index.ts +84 -0
- package/src/loader.rsc.ts +204 -0
- package/src/loader.ts +47 -0
- package/src/network-error-thrower.tsx +21 -0
- package/src/outlet-context.ts +15 -0
- package/src/root-error-boundary.tsx +277 -0
- package/src/route-content-wrapper.tsx +198 -0
- package/src/route-definition.ts +1333 -0
- package/src/route-map-builder.ts +140 -0
- package/src/route-types.ts +148 -0
- package/src/route-utils.ts +89 -0
- package/src/router/__tests__/match-context.test.ts +104 -0
- package/src/router/__tests__/match-pipelines.test.ts +537 -0
- package/src/router/__tests__/match-result.test.ts +566 -0
- package/src/router/__tests__/on-error.test.ts +935 -0
- package/src/router/__tests__/pattern-matching.test.ts +577 -0
- package/src/router/error-handling.ts +287 -0
- package/src/router/handler-context.ts +60 -0
- package/src/router/loader-resolution.ts +326 -0
- package/src/router/manifest.ts +116 -0
- package/src/router/match-context.ts +261 -0
- package/src/router/match-middleware/background-revalidation.ts +236 -0
- package/src/router/match-middleware/cache-lookup.ts +261 -0
- package/src/router/match-middleware/cache-store.ts +250 -0
- package/src/router/match-middleware/index.ts +81 -0
- package/src/router/match-middleware/intercept-resolution.ts +268 -0
- package/src/router/match-middleware/segment-resolution.ts +174 -0
- package/src/router/match-pipelines.ts +214 -0
- package/src/router/match-result.ts +212 -0
- package/src/router/metrics.ts +62 -0
- package/src/router/middleware.test.ts +1355 -0
- package/src/router/middleware.ts +748 -0
- package/src/router/pattern-matching.ts +271 -0
- package/src/router/revalidation.ts +190 -0
- package/src/router/router-context.ts +299 -0
- package/src/router/types.ts +96 -0
- package/src/router.ts +3484 -0
- package/src/rsc/__tests__/helpers.test.ts +175 -0
- package/src/rsc/handler.ts +942 -0
- package/src/rsc/helpers.ts +64 -0
- package/src/rsc/index.ts +56 -0
- package/src/rsc/nonce.ts +18 -0
- package/src/rsc/types.ts +225 -0
- package/src/segment-system.tsx +405 -0
- package/src/server/__tests__/request-context.test.ts +171 -0
- package/src/server/context.ts +340 -0
- package/src/server/handle-store.ts +230 -0
- package/src/server/loader-registry.ts +174 -0
- package/src/server/request-context.ts +470 -0
- package/src/server/root-layout.tsx +10 -0
- package/src/server/tsconfig.json +14 -0
- package/src/server.ts +126 -0
- package/src/ssr/__tests__/ssr-handler.test.tsx +188 -0
- package/src/ssr/index.tsx +215 -0
- package/src/types.ts +1473 -0
- package/src/use-loader.tsx +346 -0
- package/src/vite/__tests__/expose-loader-id.test.ts +117 -0
- package/src/vite/expose-action-id.ts +344 -0
- package/src/vite/expose-handle-id.ts +209 -0
- package/src/vite/expose-loader-id.ts +357 -0
- package/src/vite/expose-location-state-id.ts +177 -0
- package/src/vite/index.ts +608 -0
- package/src/vite/version.d.ts +12 -0
- package/src/vite/virtual-entries.ts +109 -0
|
@@ -0,0 +1,150 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* In-Memory Segment Cache Store
|
|
3
|
+
*
|
|
4
|
+
* Simple in-memory implementation of SegmentCacheStore.
|
|
5
|
+
* Uses globalThis to survive HMR in development.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import type { SegmentCacheStore, CachedEntryData, CacheDefaults, CacheGetResult } from "./types.js";
|
|
9
|
+
import type { RequestContext } from "../server/request-context.js";
|
|
10
|
+
|
|
11
|
+
const CACHE_GLOBAL_KEY = "__rsc_router_segment_cache_store__";
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* Options for MemorySegmentCacheStore
|
|
15
|
+
*/
|
|
16
|
+
export interface MemorySegmentCacheStoreOptions<TEnv = unknown> {
|
|
17
|
+
/**
|
|
18
|
+
* Default cache options for cache() boundaries.
|
|
19
|
+
* When cache() is called without explicit ttl/swr,
|
|
20
|
+
* these defaults are used.
|
|
21
|
+
*
|
|
22
|
+
* @example
|
|
23
|
+
* ```typescript
|
|
24
|
+
* const store = new MemorySegmentCacheStore({
|
|
25
|
+
* defaults: { ttl: 60, swr: 300 }
|
|
26
|
+
* });
|
|
27
|
+
* ```
|
|
28
|
+
*/
|
|
29
|
+
defaults?: CacheDefaults;
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* Custom key generator applied to all cache operations.
|
|
33
|
+
* Receives the full RequestContext and the default-generated key.
|
|
34
|
+
*
|
|
35
|
+
* @example
|
|
36
|
+
* ```typescript
|
|
37
|
+
* keyGenerator: (ctx, defaultKey) => {
|
|
38
|
+
* const locale = ctx.cookie('locale') || 'en';
|
|
39
|
+
* return `${locale}:${defaultKey}`;
|
|
40
|
+
* }
|
|
41
|
+
* ```
|
|
42
|
+
*/
|
|
43
|
+
keyGenerator?: (
|
|
44
|
+
ctx: RequestContext<TEnv>,
|
|
45
|
+
defaultKey: string
|
|
46
|
+
) => string | Promise<string>;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
/**
|
|
50
|
+
* In-memory segment cache store.
|
|
51
|
+
*
|
|
52
|
+
* Suitable for development and single-instance deployments.
|
|
53
|
+
* For production with multiple instances, use a distributed store
|
|
54
|
+
* like Cloudflare KV or Redis.
|
|
55
|
+
*
|
|
56
|
+
* @example
|
|
57
|
+
* ```typescript
|
|
58
|
+
* // Basic usage
|
|
59
|
+
* const store = new MemorySegmentCacheStore();
|
|
60
|
+
*
|
|
61
|
+
* // With defaults for cache() boundaries
|
|
62
|
+
* const store = new MemorySegmentCacheStore({
|
|
63
|
+
* defaults: { ttl: 60 }
|
|
64
|
+
* });
|
|
65
|
+
*
|
|
66
|
+
* createRSCHandler({
|
|
67
|
+
* router,
|
|
68
|
+
* cache: { store }
|
|
69
|
+
* })
|
|
70
|
+
* ```
|
|
71
|
+
*/
|
|
72
|
+
export class MemorySegmentCacheStore<TEnv = unknown> implements SegmentCacheStore<TEnv> {
|
|
73
|
+
private cache: Map<string, CachedEntryData>;
|
|
74
|
+
readonly defaults?: CacheDefaults;
|
|
75
|
+
readonly keyGenerator?: (
|
|
76
|
+
ctx: RequestContext<TEnv>,
|
|
77
|
+
defaultKey: string
|
|
78
|
+
) => string | Promise<string>;
|
|
79
|
+
|
|
80
|
+
constructor(options?: MemorySegmentCacheStoreOptions<TEnv>) {
|
|
81
|
+
// Use globalThis to survive HMR in development
|
|
82
|
+
this.cache =
|
|
83
|
+
(globalThis as any)[CACHE_GLOBAL_KEY] ??
|
|
84
|
+
((globalThis as any)[CACHE_GLOBAL_KEY] = new Map<string, CachedEntryData>());
|
|
85
|
+
this.defaults = options?.defaults;
|
|
86
|
+
this.keyGenerator = options?.keyGenerator;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
async get(key: string): Promise<CacheGetResult | null> {
|
|
90
|
+
const cached = this.cache.get(key);
|
|
91
|
+
|
|
92
|
+
if (!cached) {
|
|
93
|
+
return null;
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
// Check expiration
|
|
97
|
+
if (Date.now() > cached.expiresAt) {
|
|
98
|
+
this.cache.delete(key);
|
|
99
|
+
return null;
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
// Memory store doesn't support SWR - never triggers revalidation
|
|
103
|
+
return { data: cached, shouldRevalidate: false };
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
async set(key: string, data: CachedEntryData, ttl: number, _swr?: number): Promise<void> {
|
|
107
|
+
// Note: Memory store doesn't implement SWR - entries just expire at TTL
|
|
108
|
+
// For SWR support, use CFCacheStore or similar distributed cache
|
|
109
|
+
const entry: CachedEntryData = {
|
|
110
|
+
...data,
|
|
111
|
+
expiresAt: Date.now() + ttl * 1000,
|
|
112
|
+
};
|
|
113
|
+
this.cache.set(key, entry);
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
async delete(key: string): Promise<boolean> {
|
|
117
|
+
return this.cache.delete(key);
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
async clear(): Promise<void> {
|
|
121
|
+
this.cache.clear();
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
/**
|
|
125
|
+
* Get cache statistics for debugging purposes.
|
|
126
|
+
* @internal
|
|
127
|
+
*/
|
|
128
|
+
getStats(): { size: number; keys: string[] } {
|
|
129
|
+
return {
|
|
130
|
+
size: this.cache.size,
|
|
131
|
+
keys: Array.from(this.cache.keys()),
|
|
132
|
+
};
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
/**
|
|
136
|
+
* Reset the global cache state.
|
|
137
|
+
* Useful for test isolation - call this in beforeEach to ensure
|
|
138
|
+
* tests don't share cache state via globalThis.
|
|
139
|
+
*
|
|
140
|
+
* @example
|
|
141
|
+
* ```typescript
|
|
142
|
+
* beforeEach(() => {
|
|
143
|
+
* MemorySegmentCacheStore.resetGlobalCache();
|
|
144
|
+
* });
|
|
145
|
+
* ```
|
|
146
|
+
*/
|
|
147
|
+
static resetGlobalCache(): void {
|
|
148
|
+
delete (globalThis as any)[CACHE_GLOBAL_KEY];
|
|
149
|
+
}
|
|
150
|
+
}
|
|
@@ -0,0 +1,253 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* In-Memory Cache Store
|
|
3
|
+
*
|
|
4
|
+
* Simple implementation for development and testing.
|
|
5
|
+
* Not suitable for production (no persistence, single-instance only).
|
|
6
|
+
*
|
|
7
|
+
* @internal This is reserved for future extensibility.
|
|
8
|
+
* For segment caching, use MemorySegmentCacheStore instead.
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
import type {
|
|
12
|
+
CacheStore,
|
|
13
|
+
CacheEntry,
|
|
14
|
+
CacheValue,
|
|
15
|
+
CachePutOptions,
|
|
16
|
+
CacheMetadata,
|
|
17
|
+
CacheValueType,
|
|
18
|
+
} from "./types.js";
|
|
19
|
+
|
|
20
|
+
// ============================================================================
|
|
21
|
+
// Constants
|
|
22
|
+
// ============================================================================
|
|
23
|
+
|
|
24
|
+
/** Default TTL when no explicit value is provided */
|
|
25
|
+
const DEFAULT_TTL_SECONDS = 60;
|
|
26
|
+
|
|
27
|
+
// ============================================================================
|
|
28
|
+
// Types
|
|
29
|
+
// ============================================================================
|
|
30
|
+
|
|
31
|
+
interface StoredEntry {
|
|
32
|
+
/** Stored value (streams/responses converted to ArrayBuffer) */
|
|
33
|
+
value: ArrayBuffer | string | object;
|
|
34
|
+
metadata: CacheMetadata;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* In-memory cache store implementation
|
|
39
|
+
*/
|
|
40
|
+
export class MemoryCacheStore implements CacheStore {
|
|
41
|
+
private cache = new Map<string, StoredEntry>();
|
|
42
|
+
|
|
43
|
+
async match<T = CacheValue>(key: string): Promise<CacheEntry<T> | undefined> {
|
|
44
|
+
const entry = this.cache.get(key);
|
|
45
|
+
|
|
46
|
+
if (!entry) {
|
|
47
|
+
return undefined;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
// Check expiration
|
|
51
|
+
if (entry.metadata.expiresAt && Date.now() > entry.metadata.expiresAt) {
|
|
52
|
+
this.cache.delete(key);
|
|
53
|
+
return undefined;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
// Reconstruct value based on original type
|
|
57
|
+
const value = this.reconstructValue(entry);
|
|
58
|
+
|
|
59
|
+
return {
|
|
60
|
+
value: value as T,
|
|
61
|
+
metadata: entry.metadata,
|
|
62
|
+
};
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
async put<T extends CacheValue>(
|
|
66
|
+
key: string,
|
|
67
|
+
value: T,
|
|
68
|
+
options?: CachePutOptions
|
|
69
|
+
): Promise<void> {
|
|
70
|
+
const ttl = options?.ttl ?? DEFAULT_TTL_SECONDS;
|
|
71
|
+
const expiresAt = Date.now() + ttl * 1000;
|
|
72
|
+
|
|
73
|
+
// Detect value type and convert for storage
|
|
74
|
+
const { storedValue, valueType, responseHeaders, responseStatus } =
|
|
75
|
+
await this.prepareForStorage(value);
|
|
76
|
+
|
|
77
|
+
const metadata: CacheMetadata = {
|
|
78
|
+
...options?.metadata,
|
|
79
|
+
expiresAt,
|
|
80
|
+
valueType,
|
|
81
|
+
responseHeaders,
|
|
82
|
+
responseStatus,
|
|
83
|
+
};
|
|
84
|
+
|
|
85
|
+
this.cache.set(key, {
|
|
86
|
+
value: storedValue,
|
|
87
|
+
metadata,
|
|
88
|
+
});
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
async delete(key: string): Promise<boolean> {
|
|
92
|
+
return this.cache.delete(key);
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
/**
|
|
96
|
+
* Clear all entries (useful for testing)
|
|
97
|
+
*/
|
|
98
|
+
clear(): void {
|
|
99
|
+
this.cache.clear();
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
/**
|
|
103
|
+
* Get current cache size (useful for testing/debugging)
|
|
104
|
+
*/
|
|
105
|
+
get size(): number {
|
|
106
|
+
return this.cache.size;
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
/**
|
|
110
|
+
* Manually purge expired entries
|
|
111
|
+
*/
|
|
112
|
+
purgeExpired(): number {
|
|
113
|
+
const now = Date.now();
|
|
114
|
+
let purged = 0;
|
|
115
|
+
|
|
116
|
+
for (const [key, entry] of this.cache) {
|
|
117
|
+
if (entry.metadata.expiresAt && now > entry.metadata.expiresAt) {
|
|
118
|
+
this.cache.delete(key);
|
|
119
|
+
purged++;
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
return purged;
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
/**
|
|
127
|
+
* Prepare a value for storage
|
|
128
|
+
* Converts streams and responses to ArrayBuffer, detects type
|
|
129
|
+
*/
|
|
130
|
+
private async prepareForStorage(value: CacheValue): Promise<{
|
|
131
|
+
storedValue: ArrayBuffer | string | object;
|
|
132
|
+
valueType: CacheValueType;
|
|
133
|
+
responseHeaders?: Record<string, string>;
|
|
134
|
+
responseStatus?: number;
|
|
135
|
+
}> {
|
|
136
|
+
// ReadableStream -> ArrayBuffer
|
|
137
|
+
if (value instanceof ReadableStream) {
|
|
138
|
+
return {
|
|
139
|
+
storedValue: await streamToArrayBuffer(value),
|
|
140
|
+
valueType: "stream",
|
|
141
|
+
};
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
// Response -> ArrayBuffer + headers/status
|
|
145
|
+
if (value instanceof Response) {
|
|
146
|
+
const headers: Record<string, string> = {};
|
|
147
|
+
value.headers.forEach((v, k) => {
|
|
148
|
+
headers[k] = v;
|
|
149
|
+
});
|
|
150
|
+
|
|
151
|
+
return {
|
|
152
|
+
storedValue: await value.clone().arrayBuffer(),
|
|
153
|
+
valueType: "response",
|
|
154
|
+
responseHeaders: headers,
|
|
155
|
+
responseStatus: value.status,
|
|
156
|
+
};
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
// ArrayBuffer -> store as-is
|
|
160
|
+
if (value instanceof ArrayBuffer) {
|
|
161
|
+
return {
|
|
162
|
+
storedValue: value,
|
|
163
|
+
valueType: "arraybuffer",
|
|
164
|
+
};
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
// String -> store as-is
|
|
168
|
+
if (typeof value === "string") {
|
|
169
|
+
return {
|
|
170
|
+
storedValue: value,
|
|
171
|
+
valueType: "string",
|
|
172
|
+
};
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
// Object -> store as-is (JSON-serializable)
|
|
176
|
+
return {
|
|
177
|
+
storedValue: value,
|
|
178
|
+
valueType: "object",
|
|
179
|
+
};
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
/**
|
|
183
|
+
* Reconstruct original value type from stored entry
|
|
184
|
+
*/
|
|
185
|
+
private reconstructValue(entry: StoredEntry): CacheValue {
|
|
186
|
+
const { value, metadata } = entry;
|
|
187
|
+
|
|
188
|
+
switch (metadata.valueType) {
|
|
189
|
+
case "stream":
|
|
190
|
+
return arrayBufferToStream(value as ArrayBuffer);
|
|
191
|
+
|
|
192
|
+
case "response": {
|
|
193
|
+
const status = metadata.responseStatus ?? 200;
|
|
194
|
+
// Status codes 204 (No Content) and 304 (Not Modified) cannot have a body
|
|
195
|
+
const isNullBodyStatus = status === 204 || status === 304;
|
|
196
|
+
return new Response(isNullBodyStatus ? null : (value as ArrayBuffer), {
|
|
197
|
+
status,
|
|
198
|
+
headers: metadata.responseHeaders,
|
|
199
|
+
});
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
case "arraybuffer":
|
|
203
|
+
case "string":
|
|
204
|
+
case "object":
|
|
205
|
+
default:
|
|
206
|
+
return value as CacheValue;
|
|
207
|
+
}
|
|
208
|
+
}
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
/**
|
|
212
|
+
* Convert a ReadableStream to ArrayBuffer.
|
|
213
|
+
* @internal
|
|
214
|
+
*/
|
|
215
|
+
async function streamToArrayBuffer(
|
|
216
|
+
stream: ReadableStream<Uint8Array>
|
|
217
|
+
): Promise<ArrayBuffer> {
|
|
218
|
+
const chunks: Uint8Array[] = [];
|
|
219
|
+
const reader = stream.getReader();
|
|
220
|
+
|
|
221
|
+
while (true) {
|
|
222
|
+
const { done, value } = await reader.read();
|
|
223
|
+
if (done) break;
|
|
224
|
+
chunks.push(value);
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
// Concatenate chunks
|
|
228
|
+
const totalLength = chunks.reduce((sum, chunk) => sum + chunk.length, 0);
|
|
229
|
+
const result = new Uint8Array(totalLength);
|
|
230
|
+
let offset = 0;
|
|
231
|
+
|
|
232
|
+
for (const chunk of chunks) {
|
|
233
|
+
result.set(chunk, offset);
|
|
234
|
+
offset += chunk.length;
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
return result.buffer;
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
/**
|
|
241
|
+
* Convert an ArrayBuffer to a ReadableStream.
|
|
242
|
+
* @internal
|
|
243
|
+
*/
|
|
244
|
+
function arrayBufferToStream(buffer: ArrayBuffer): ReadableStream<Uint8Array> {
|
|
245
|
+
const uint8 = new Uint8Array(buffer);
|
|
246
|
+
|
|
247
|
+
return new ReadableStream({
|
|
248
|
+
start(controller) {
|
|
249
|
+
controller.enqueue(uint8);
|
|
250
|
+
controller.close();
|
|
251
|
+
},
|
|
252
|
+
});
|
|
253
|
+
}
|