@react-router/node 7.15.1 → 8.0.0-pre.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/CHANGELOG.md +11 -0
- package/dist/index.d.ts +30 -23
- package/dist/index.js +270 -256
- package/package.json +14 -22
- package/dist/index.d.mts +0 -48
- package/dist/index.mjs +0 -238
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,16 @@
|
|
|
1
1
|
# `@react-router/node`
|
|
2
2
|
|
|
3
|
+
## v7.16.0
|
|
4
|
+
|
|
5
|
+
### Patch Changes
|
|
6
|
+
|
|
7
|
+
- Honor Node writable backpressure in `writeReadableStreamToWritable` and `writeAsyncIterableToWritable` ([#15071](https://github.com/remix-run/react-router/pull/15071))
|
|
8
|
+
- Await `'drain'` when `writable.write()` returns `false` instead of letting chunks accumulate in the writable's internal buffer.
|
|
9
|
+
- Reject (rather than hang) if the writable errors or closes mid-stream.
|
|
10
|
+
|
|
11
|
+
- Updated dependencies:
|
|
12
|
+
- [`react-router@7.16.0`](https://github.com/remix-run/react-router/releases/tag/react-router@7.16.0)
|
|
13
|
+
|
|
3
14
|
## v7.15.1
|
|
4
15
|
|
|
5
16
|
### Patch Changes
|
package/dist/index.d.ts
CHANGED
|
@@ -1,13 +1,15 @@
|
|
|
1
|
-
import { RequestListener } from 'node:http';
|
|
2
|
-
import { ServerBuild, UNSAFE_MiddlewareEnabled, RouterContextProvider, AppLoadContext, SessionData, SessionIdStorageStrategy, SessionStorage } from 'react-router';
|
|
3
|
-
import { ClientAddress } from '@mjackson/node-fetch-server';
|
|
4
|
-
import { Readable, Writable } from 'node:stream';
|
|
5
1
|
|
|
2
|
+
import { ClientAddress } from "@remix-run/node-fetch-server";
|
|
3
|
+
import { RouterContextProvider, ServerBuild, SessionData, SessionIdStorageStrategy, SessionStorage } from "react-router";
|
|
4
|
+
import { Readable, Writable } from "node:stream";
|
|
5
|
+
import { RequestListener } from "node:http";
|
|
6
|
+
|
|
7
|
+
//#region server.d.ts
|
|
6
8
|
type MaybePromise<T> = T | Promise<T>;
|
|
7
9
|
interface RequestListenerOptions {
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
10
|
+
build: ServerBuild | (() => ServerBuild | Promise<ServerBuild>);
|
|
11
|
+
getLoadContext?: (request: Request, client: ClientAddress) => MaybePromise<RouterContextProvider>;
|
|
12
|
+
mode?: string;
|
|
11
13
|
}
|
|
12
14
|
/**
|
|
13
15
|
* Creates a request listener that handles requests using Node's built-in HTTP server.
|
|
@@ -16,17 +18,18 @@ interface RequestListenerOptions {
|
|
|
16
18
|
* @returns A request listener that can be used with `http.createServer`.
|
|
17
19
|
*/
|
|
18
20
|
declare function createRequestListener(options: RequestListenerOptions): RequestListener;
|
|
19
|
-
|
|
21
|
+
//#endregion
|
|
22
|
+
//#region sessions/fileStorage.d.ts
|
|
20
23
|
interface FileSessionStorageOptions {
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
24
|
+
/**
|
|
25
|
+
* The Cookie used to store the session id on the client, or options used
|
|
26
|
+
* to automatically create one.
|
|
27
|
+
*/
|
|
28
|
+
cookie?: SessionIdStorageStrategy["cookie"];
|
|
29
|
+
/**
|
|
30
|
+
* The directory to use to store session files.
|
|
31
|
+
*/
|
|
32
|
+
dir: string;
|
|
30
33
|
}
|
|
31
34
|
/**
|
|
32
35
|
* Creates a SessionStorage that stores session data on a filesystem.
|
|
@@ -36,13 +39,17 @@ interface FileSessionStorageOptions {
|
|
|
36
39
|
*
|
|
37
40
|
* @see https://api.reactrouter.com/v7/functions/_react-router_node.createFileSessionStorage
|
|
38
41
|
*/
|
|
39
|
-
declare function createFileSessionStorage<Data = SessionData, FlashData = Data>({
|
|
40
|
-
|
|
42
|
+
declare function createFileSessionStorage<Data = SessionData, FlashData = Data>({
|
|
43
|
+
cookie,
|
|
44
|
+
dir
|
|
45
|
+
}: FileSessionStorageOptions): SessionStorage<Data, FlashData>;
|
|
46
|
+
//#endregion
|
|
47
|
+
//#region stream.d.ts
|
|
41
48
|
declare function writeReadableStreamToWritable(stream: ReadableStream, writable: Writable): Promise<void>;
|
|
42
49
|
declare function writeAsyncIterableToWritable(iterable: AsyncIterable<Uint8Array>, writable: Writable): Promise<void>;
|
|
43
50
|
declare function readableStreamToString(stream: ReadableStream<Uint8Array>, encoding?: BufferEncoding): Promise<string>;
|
|
44
51
|
declare const createReadableStreamFromReadable: (source: Readable & {
|
|
45
|
-
|
|
46
|
-
}) => ReadableStream<Uint8Array
|
|
47
|
-
|
|
48
|
-
export { type RequestListenerOptions, createFileSessionStorage, createReadableStreamFromReadable, createRequestListener, readableStreamToString, writeAsyncIterableToWritable, writeReadableStreamToWritable };
|
|
52
|
+
readableHighWaterMark?: number;
|
|
53
|
+
}) => ReadableStream<Uint8Array<ArrayBufferLike>>;
|
|
54
|
+
//#endregion
|
|
55
|
+
export { type RequestListenerOptions, createFileSessionStorage, createReadableStreamFromReadable, createRequestListener, readableStreamToString, writeAsyncIterableToWritable, writeReadableStreamToWritable };
|
package/dist/index.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* @react-router/node
|
|
2
|
+
* @react-router/node v8.0.0-pre.0
|
|
3
3
|
*
|
|
4
4
|
* Copyright (c) Remix Software Inc.
|
|
5
5
|
*
|
|
@@ -8,272 +8,286 @@
|
|
|
8
8
|
*
|
|
9
9
|
* @license MIT
|
|
10
10
|
*/
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
if (from && typeof from === "object" || typeof from === "function") {
|
|
24
|
-
for (let key of __getOwnPropNames(from))
|
|
25
|
-
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
26
|
-
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
27
|
-
}
|
|
28
|
-
return to;
|
|
29
|
-
};
|
|
30
|
-
var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
|
|
31
|
-
// If the importer is in node compatibility mode or this is not an ESM
|
|
32
|
-
// file that has been converted to a CommonJS file using a Babel-
|
|
33
|
-
// compatible transform (i.e. "__esModule" has not been set), then set
|
|
34
|
-
// "default" to the CommonJS "module.exports" for node compatibility.
|
|
35
|
-
isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
|
|
36
|
-
mod
|
|
37
|
-
));
|
|
38
|
-
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
39
|
-
|
|
40
|
-
// index.ts
|
|
41
|
-
var index_exports = {};
|
|
42
|
-
__export(index_exports, {
|
|
43
|
-
createFileSessionStorage: () => createFileSessionStorage,
|
|
44
|
-
createReadableStreamFromReadable: () => createReadableStreamFromReadable,
|
|
45
|
-
createRequestListener: () => createRequestListener,
|
|
46
|
-
readableStreamToString: () => readableStreamToString,
|
|
47
|
-
writeAsyncIterableToWritable: () => writeAsyncIterableToWritable,
|
|
48
|
-
writeReadableStreamToWritable: () => writeReadableStreamToWritable
|
|
49
|
-
});
|
|
50
|
-
module.exports = __toCommonJS(index_exports);
|
|
51
|
-
|
|
52
|
-
// server.ts
|
|
53
|
-
var import_react_router = require("react-router");
|
|
54
|
-
var import_node_fetch_server = require("@mjackson/node-fetch-server");
|
|
11
|
+
import { createRequestListener as createRequestListener$1 } from "@remix-run/node-fetch-server";
|
|
12
|
+
import { createRequestHandler, createSessionStorage } from "react-router";
|
|
13
|
+
import { promises } from "node:fs";
|
|
14
|
+
import * as path from "node:path";
|
|
15
|
+
import { Stream } from "node:stream";
|
|
16
|
+
//#region server.ts
|
|
17
|
+
/**
|
|
18
|
+
* Creates a request listener that handles requests using Node's built-in HTTP server.
|
|
19
|
+
*
|
|
20
|
+
* @param options Options for creating a request listener.
|
|
21
|
+
* @returns A request listener that can be used with `http.createServer`.
|
|
22
|
+
*/
|
|
55
23
|
function createRequestListener(options) {
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
});
|
|
24
|
+
let handleRequest = createRequestHandler(options.build, options.mode);
|
|
25
|
+
return createRequestListener$1(async (request, client) => {
|
|
26
|
+
return handleRequest(request, await options.getLoadContext?.(request, client));
|
|
27
|
+
});
|
|
61
28
|
}
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
});
|
|
29
|
+
//#endregion
|
|
30
|
+
//#region sessions/fileStorage.ts
|
|
31
|
+
/**
|
|
32
|
+
* Creates a SessionStorage that stores session data on a filesystem.
|
|
33
|
+
*
|
|
34
|
+
* The advantage of using this instead of cookie session storage is that
|
|
35
|
+
* files may contain much more data than cookies.
|
|
36
|
+
*
|
|
37
|
+
* @see https://api.reactrouter.com/v7/functions/_react-router_node.createFileSessionStorage
|
|
38
|
+
*/
|
|
39
|
+
function createFileSessionStorage({ cookie, dir }) {
|
|
40
|
+
return createSessionStorage({
|
|
41
|
+
cookie,
|
|
42
|
+
async createData(data, expires) {
|
|
43
|
+
let content = JSON.stringify({
|
|
44
|
+
data,
|
|
45
|
+
expires
|
|
46
|
+
});
|
|
47
|
+
while (true) {
|
|
48
|
+
let randomBytes = crypto.getRandomValues(new Uint8Array(8));
|
|
49
|
+
let id = Buffer.from(randomBytes).toString("hex");
|
|
50
|
+
try {
|
|
51
|
+
let file = getFile(dir, id);
|
|
52
|
+
if (!file) throw new Error("Error generating session");
|
|
53
|
+
await promises.mkdir(path.dirname(file), { recursive: true });
|
|
54
|
+
await promises.writeFile(file, content, {
|
|
55
|
+
encoding: "utf-8",
|
|
56
|
+
flag: "wx"
|
|
57
|
+
});
|
|
58
|
+
return id;
|
|
59
|
+
} catch (error) {
|
|
60
|
+
if (error.code !== "EEXIST") throw error;
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
},
|
|
64
|
+
async readData(id) {
|
|
65
|
+
try {
|
|
66
|
+
let file = getFile(dir, id);
|
|
67
|
+
if (!file) return null;
|
|
68
|
+
let content = JSON.parse(await promises.readFile(file, "utf-8"));
|
|
69
|
+
let data = content.data;
|
|
70
|
+
let expires = typeof content.expires === "string" ? new Date(content.expires) : null;
|
|
71
|
+
if (!expires || expires > /* @__PURE__ */ new Date()) return data;
|
|
72
|
+
if (expires) await promises.unlink(file);
|
|
73
|
+
return null;
|
|
74
|
+
} catch (error) {
|
|
75
|
+
if (error.code !== "ENOENT") throw error;
|
|
76
|
+
return null;
|
|
77
|
+
}
|
|
78
|
+
},
|
|
79
|
+
async updateData(id, data, expires) {
|
|
80
|
+
let content = JSON.stringify({
|
|
81
|
+
data,
|
|
82
|
+
expires
|
|
83
|
+
});
|
|
84
|
+
let file = getFile(dir, id);
|
|
85
|
+
if (!file) return;
|
|
86
|
+
await promises.mkdir(path.dirname(file), { recursive: true });
|
|
87
|
+
await promises.writeFile(file, content, "utf-8");
|
|
88
|
+
},
|
|
89
|
+
async deleteData(id) {
|
|
90
|
+
if (!id) return;
|
|
91
|
+
let file = getFile(dir, id);
|
|
92
|
+
if (!file) return;
|
|
93
|
+
try {
|
|
94
|
+
await promises.unlink(file);
|
|
95
|
+
} catch (error) {
|
|
96
|
+
if (error.code !== "ENOENT") throw error;
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
});
|
|
134
100
|
}
|
|
135
101
|
function getFile(dir, id) {
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
}
|
|
139
|
-
return path.join(dir, id.slice(0, 4), id.slice(4));
|
|
102
|
+
if (!/^[0-9a-f]{16}$/i.test(id)) return null;
|
|
103
|
+
return path.join(dir, id.slice(0, 4), id.slice(4));
|
|
140
104
|
}
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
var import_node_stream = require("stream");
|
|
105
|
+
//#endregion
|
|
106
|
+
//#region stream.ts
|
|
144
107
|
async function writeReadableStreamToWritable(stream, writable) {
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
108
|
+
let reader = stream.getReader();
|
|
109
|
+
let flushable = writable;
|
|
110
|
+
let writableError = monitorWritableError(writable);
|
|
111
|
+
try {
|
|
112
|
+
while (true) {
|
|
113
|
+
writableError.throwIfClosed();
|
|
114
|
+
let { done, value } = await writableError.race(reader.read());
|
|
115
|
+
if (done) {
|
|
116
|
+
writable.end();
|
|
117
|
+
break;
|
|
118
|
+
}
|
|
119
|
+
writableError.throwIfClosed();
|
|
120
|
+
let canContinueWriting = writable.write(value);
|
|
121
|
+
if (typeof flushable.flush === "function") flushable.flush();
|
|
122
|
+
if (!canContinueWriting) await waitForDrain(writable, writableError);
|
|
123
|
+
}
|
|
124
|
+
} catch (error) {
|
|
125
|
+
try {
|
|
126
|
+
reader.cancel(error).catch(() => {});
|
|
127
|
+
} catch {}
|
|
128
|
+
writable.destroy(error);
|
|
129
|
+
throw error;
|
|
130
|
+
} finally {
|
|
131
|
+
writableError.cleanup();
|
|
132
|
+
try {
|
|
133
|
+
reader.releaseLock();
|
|
134
|
+
} catch {}
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
function monitorWritableError(writable) {
|
|
138
|
+
let settled = false;
|
|
139
|
+
let writableError;
|
|
140
|
+
let rejectWritableError;
|
|
141
|
+
let writableErrorPromise = new Promise((_, reject) => {
|
|
142
|
+
rejectWritableError = reject;
|
|
143
|
+
});
|
|
144
|
+
writableErrorPromise.catch(() => {});
|
|
145
|
+
function cleanup() {
|
|
146
|
+
writable.off("error", onError);
|
|
147
|
+
writable.off("close", onClose);
|
|
148
|
+
}
|
|
149
|
+
function reject(error) {
|
|
150
|
+
if (settled) return;
|
|
151
|
+
settled = true;
|
|
152
|
+
writableError = error;
|
|
153
|
+
cleanup();
|
|
154
|
+
rejectWritableError(error);
|
|
155
|
+
}
|
|
156
|
+
function onError(error) {
|
|
157
|
+
reject(error);
|
|
158
|
+
}
|
|
159
|
+
function onClose() {
|
|
160
|
+
reject(/* @__PURE__ */ new Error("Writable closed before stream finished"));
|
|
161
|
+
}
|
|
162
|
+
writable.once("error", onError);
|
|
163
|
+
writable.once("close", onClose);
|
|
164
|
+
return {
|
|
165
|
+
cleanup,
|
|
166
|
+
race(promise) {
|
|
167
|
+
return Promise.race([promise, writableErrorPromise]);
|
|
168
|
+
},
|
|
169
|
+
throwIfClosed() {
|
|
170
|
+
if (writableError) throw writableError;
|
|
171
|
+
if (writable.destroyed || writable.writableEnded) throw new Error("Cannot write to a destroyed or ended writable stream");
|
|
172
|
+
}
|
|
173
|
+
};
|
|
174
|
+
}
|
|
175
|
+
function waitForDrain(writable, writableError) {
|
|
176
|
+
let cleanup = () => {};
|
|
177
|
+
let drainPromise = new Promise((resolve) => {
|
|
178
|
+
function onDrain() {
|
|
179
|
+
cleanup();
|
|
180
|
+
resolve();
|
|
181
|
+
}
|
|
182
|
+
cleanup = function cleanup() {
|
|
183
|
+
writable.off("drain", onDrain);
|
|
184
|
+
};
|
|
185
|
+
writable.once("drain", onDrain);
|
|
186
|
+
});
|
|
187
|
+
return writableError.race(drainPromise).finally(cleanup);
|
|
163
188
|
}
|
|
164
189
|
async function writeAsyncIterableToWritable(iterable, writable) {
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
190
|
+
let writableError = monitorWritableError(writable);
|
|
191
|
+
let iterator = iterable[Symbol.asyncIterator]();
|
|
192
|
+
let completed = false;
|
|
193
|
+
try {
|
|
194
|
+
while (true) {
|
|
195
|
+
writableError.throwIfClosed();
|
|
196
|
+
let { done, value: chunk } = await writableError.race(iterator.next());
|
|
197
|
+
if (done) {
|
|
198
|
+
completed = true;
|
|
199
|
+
break;
|
|
200
|
+
}
|
|
201
|
+
writableError.throwIfClosed();
|
|
202
|
+
if (!writable.write(chunk)) await waitForDrain(writable, writableError);
|
|
203
|
+
}
|
|
204
|
+
writable.end();
|
|
205
|
+
} catch (error) {
|
|
206
|
+
if (!completed) try {
|
|
207
|
+
Promise.resolve(iterator.return?.()).catch(() => {});
|
|
208
|
+
} catch {}
|
|
209
|
+
writable.destroy(error);
|
|
210
|
+
throw error;
|
|
211
|
+
} finally {
|
|
212
|
+
writableError.cleanup();
|
|
213
|
+
}
|
|
174
214
|
}
|
|
175
215
|
async function readableStreamToString(stream, encoding) {
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
chunks.push(value);
|
|
185
|
-
}
|
|
186
|
-
}
|
|
187
|
-
return Buffer.concat(chunks).toString(encoding);
|
|
216
|
+
let reader = stream.getReader();
|
|
217
|
+
let chunks = [];
|
|
218
|
+
while (true) {
|
|
219
|
+
let { done, value } = await reader.read();
|
|
220
|
+
if (done) break;
|
|
221
|
+
if (value) chunks.push(value);
|
|
222
|
+
}
|
|
223
|
+
return Buffer.concat(chunks).toString(encoding);
|
|
188
224
|
}
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
return stream;
|
|
225
|
+
const createReadableStreamFromReadable = (source) => {
|
|
226
|
+
let pump = new StreamPump(source);
|
|
227
|
+
return new ReadableStream(pump, pump);
|
|
193
228
|
};
|
|
194
229
|
var StreamPump = class {
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
}
|
|
257
|
-
}
|
|
258
|
-
close() {
|
|
259
|
-
if (this.controller) {
|
|
260
|
-
this.controller.close();
|
|
261
|
-
delete this.controller;
|
|
262
|
-
}
|
|
263
|
-
}
|
|
264
|
-
error(error) {
|
|
265
|
-
if (this.controller) {
|
|
266
|
-
this.controller.error(error);
|
|
267
|
-
delete this.controller;
|
|
268
|
-
}
|
|
269
|
-
}
|
|
230
|
+
highWaterMark;
|
|
231
|
+
accumulatedSize;
|
|
232
|
+
stream;
|
|
233
|
+
controller;
|
|
234
|
+
constructor(stream) {
|
|
235
|
+
this.highWaterMark = stream.readableHighWaterMark || new Stream.Readable().readableHighWaterMark;
|
|
236
|
+
this.accumulatedSize = 0;
|
|
237
|
+
this.stream = stream;
|
|
238
|
+
this.enqueue = this.enqueue.bind(this);
|
|
239
|
+
this.error = this.error.bind(this);
|
|
240
|
+
this.close = this.close.bind(this);
|
|
241
|
+
}
|
|
242
|
+
size(chunk) {
|
|
243
|
+
return chunk?.byteLength || 0;
|
|
244
|
+
}
|
|
245
|
+
start(controller) {
|
|
246
|
+
this.controller = controller;
|
|
247
|
+
this.stream.on("data", this.enqueue);
|
|
248
|
+
this.stream.once("error", this.error);
|
|
249
|
+
this.stream.once("end", this.close);
|
|
250
|
+
this.stream.once("close", this.close);
|
|
251
|
+
}
|
|
252
|
+
pull() {
|
|
253
|
+
this.resume();
|
|
254
|
+
}
|
|
255
|
+
cancel(reason) {
|
|
256
|
+
if (this.stream.destroy) this.stream.destroy(reason);
|
|
257
|
+
this.stream.off("data", this.enqueue);
|
|
258
|
+
this.stream.off("error", this.error);
|
|
259
|
+
this.stream.off("end", this.close);
|
|
260
|
+
this.stream.off("close", this.close);
|
|
261
|
+
}
|
|
262
|
+
enqueue(chunk) {
|
|
263
|
+
if (this.controller) try {
|
|
264
|
+
let bytes = typeof chunk === "string" ? Buffer.from(chunk) : new Uint8Array(chunk);
|
|
265
|
+
let available = (this.controller.desiredSize || 0) - bytes.byteLength;
|
|
266
|
+
this.controller.enqueue(bytes);
|
|
267
|
+
if (available <= 0) this.pause();
|
|
268
|
+
} catch (e) {
|
|
269
|
+
this.controller.error(/* @__PURE__ */ new Error("Could not create Buffer, chunk must be of type string or an instance of Buffer, ArrayBuffer, or Array or an Array-like Object"));
|
|
270
|
+
this.cancel();
|
|
271
|
+
}
|
|
272
|
+
}
|
|
273
|
+
pause() {
|
|
274
|
+
if (this.stream.pause) this.stream.pause();
|
|
275
|
+
}
|
|
276
|
+
resume() {
|
|
277
|
+
if (this.stream.readable && this.stream.resume) this.stream.resume();
|
|
278
|
+
}
|
|
279
|
+
close() {
|
|
280
|
+
if (this.controller) {
|
|
281
|
+
this.controller.close();
|
|
282
|
+
delete this.controller;
|
|
283
|
+
}
|
|
284
|
+
}
|
|
285
|
+
error(error) {
|
|
286
|
+
if (this.controller) {
|
|
287
|
+
this.controller.error(error);
|
|
288
|
+
delete this.controller;
|
|
289
|
+
}
|
|
290
|
+
}
|
|
270
291
|
};
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
createFileSessionStorage,
|
|
274
|
-
createReadableStreamFromReadable,
|
|
275
|
-
createRequestListener,
|
|
276
|
-
readableStreamToString,
|
|
277
|
-
writeAsyncIterableToWritable,
|
|
278
|
-
writeReadableStreamToWritable
|
|
279
|
-
});
|
|
292
|
+
//#endregion
|
|
293
|
+
export { createFileSessionStorage, createReadableStreamFromReadable, createRequestListener, readableStreamToString, writeAsyncIterableToWritable, writeReadableStreamToWritable };
|
package/package.json
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@react-router/node",
|
|
3
|
-
"
|
|
3
|
+
"type": "module",
|
|
4
|
+
"version": "8.0.0-pre.0",
|
|
4
5
|
"description": "Node.js platform abstractions for React Router",
|
|
5
6
|
"bugs": {
|
|
6
7
|
"url": "https://github.com/remix-run/react-router/issues"
|
|
@@ -15,25 +16,15 @@
|
|
|
15
16
|
"main": "dist/index.js",
|
|
16
17
|
"exports": {
|
|
17
18
|
".": {
|
|
18
|
-
"
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
"default": "./dist/index.js"
|
|
22
|
-
},
|
|
23
|
-
"import": {
|
|
24
|
-
"types": "./dist/index.d.mts",
|
|
25
|
-
"default": "./dist/index.mjs"
|
|
26
|
-
},
|
|
27
|
-
"default": {
|
|
28
|
-
"types": "./dist/index.d.ts",
|
|
29
|
-
"default": "./dist/index.js"
|
|
30
|
-
}
|
|
19
|
+
"types": "./dist/index.d.ts",
|
|
20
|
+
"module-sync": "./dist/index.js",
|
|
21
|
+
"default": "./dist/index.js"
|
|
31
22
|
},
|
|
32
23
|
"./package.json": "./package.json"
|
|
33
24
|
},
|
|
34
25
|
"wireit": {
|
|
35
26
|
"build": {
|
|
36
|
-
"command": "
|
|
27
|
+
"command": "tsdown",
|
|
37
28
|
"files": [
|
|
38
29
|
"../../pnpm-workspace.yaml",
|
|
39
30
|
"sessions/**",
|
|
@@ -47,17 +38,18 @@
|
|
|
47
38
|
}
|
|
48
39
|
},
|
|
49
40
|
"dependencies": {
|
|
50
|
-
"@
|
|
41
|
+
"@remix-run/node-fetch-server": "^0.13.3"
|
|
51
42
|
},
|
|
52
43
|
"devDependencies": {
|
|
53
|
-
"
|
|
54
|
-
"
|
|
55
|
-
"
|
|
56
|
-
"
|
|
44
|
+
"@types/node": "^22.19.19",
|
|
45
|
+
"tsdown": "^0.22.0",
|
|
46
|
+
"typescript": "^6.0.3",
|
|
47
|
+
"wireit": "0.14.12",
|
|
48
|
+
"react-router": "8.0.0-pre.0"
|
|
57
49
|
},
|
|
58
50
|
"peerDependencies": {
|
|
59
51
|
"typescript": "^5.1.0 || ^6.0.0",
|
|
60
|
-
"react-router": "
|
|
52
|
+
"react-router": "8.0.0-pre.0"
|
|
61
53
|
},
|
|
62
54
|
"peerDependenciesMeta": {
|
|
63
55
|
"typescript": {
|
|
@@ -65,7 +57,7 @@
|
|
|
65
57
|
}
|
|
66
58
|
},
|
|
67
59
|
"engines": {
|
|
68
|
-
"node": ">=
|
|
60
|
+
"node": ">=22.12.0"
|
|
69
61
|
},
|
|
70
62
|
"files": [
|
|
71
63
|
"dist/",
|
package/dist/index.d.mts
DELETED
|
@@ -1,48 +0,0 @@
|
|
|
1
|
-
import { RequestListener } from 'node:http';
|
|
2
|
-
import { ServerBuild, UNSAFE_MiddlewareEnabled, RouterContextProvider, AppLoadContext, SessionData, SessionIdStorageStrategy, SessionStorage } from 'react-router';
|
|
3
|
-
import { ClientAddress } from '@mjackson/node-fetch-server';
|
|
4
|
-
import { Readable, Writable } from 'node:stream';
|
|
5
|
-
|
|
6
|
-
type MaybePromise<T> = T | Promise<T>;
|
|
7
|
-
interface RequestListenerOptions {
|
|
8
|
-
build: ServerBuild | (() => ServerBuild | Promise<ServerBuild>);
|
|
9
|
-
getLoadContext?: (request: Request, client: ClientAddress) => UNSAFE_MiddlewareEnabled extends true ? MaybePromise<RouterContextProvider> : MaybePromise<AppLoadContext>;
|
|
10
|
-
mode?: string;
|
|
11
|
-
}
|
|
12
|
-
/**
|
|
13
|
-
* Creates a request listener that handles requests using Node's built-in HTTP server.
|
|
14
|
-
*
|
|
15
|
-
* @param options Options for creating a request listener.
|
|
16
|
-
* @returns A request listener that can be used with `http.createServer`.
|
|
17
|
-
*/
|
|
18
|
-
declare function createRequestListener(options: RequestListenerOptions): RequestListener;
|
|
19
|
-
|
|
20
|
-
interface FileSessionStorageOptions {
|
|
21
|
-
/**
|
|
22
|
-
* The Cookie used to store the session id on the client, or options used
|
|
23
|
-
* to automatically create one.
|
|
24
|
-
*/
|
|
25
|
-
cookie?: SessionIdStorageStrategy["cookie"];
|
|
26
|
-
/**
|
|
27
|
-
* The directory to use to store session files.
|
|
28
|
-
*/
|
|
29
|
-
dir: string;
|
|
30
|
-
}
|
|
31
|
-
/**
|
|
32
|
-
* Creates a SessionStorage that stores session data on a filesystem.
|
|
33
|
-
*
|
|
34
|
-
* The advantage of using this instead of cookie session storage is that
|
|
35
|
-
* files may contain much more data than cookies.
|
|
36
|
-
*
|
|
37
|
-
* @see https://api.reactrouter.com/v7/functions/_react-router_node.createFileSessionStorage
|
|
38
|
-
*/
|
|
39
|
-
declare function createFileSessionStorage<Data = SessionData, FlashData = Data>({ cookie, dir, }: FileSessionStorageOptions): SessionStorage<Data, FlashData>;
|
|
40
|
-
|
|
41
|
-
declare function writeReadableStreamToWritable(stream: ReadableStream, writable: Writable): Promise<void>;
|
|
42
|
-
declare function writeAsyncIterableToWritable(iterable: AsyncIterable<Uint8Array>, writable: Writable): Promise<void>;
|
|
43
|
-
declare function readableStreamToString(stream: ReadableStream<Uint8Array>, encoding?: BufferEncoding): Promise<string>;
|
|
44
|
-
declare const createReadableStreamFromReadable: (source: Readable & {
|
|
45
|
-
readableHighWaterMark?: number;
|
|
46
|
-
}) => ReadableStream<Uint8Array>;
|
|
47
|
-
|
|
48
|
-
export { type RequestListenerOptions, createFileSessionStorage, createReadableStreamFromReadable, createRequestListener, readableStreamToString, writeAsyncIterableToWritable, writeReadableStreamToWritable };
|
package/dist/index.mjs
DELETED
|
@@ -1,238 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* @react-router/node v7.15.1
|
|
3
|
-
*
|
|
4
|
-
* Copyright (c) Remix Software Inc.
|
|
5
|
-
*
|
|
6
|
-
* This source code is licensed under the MIT license found in the
|
|
7
|
-
* LICENSE.md file in the root directory of this source tree.
|
|
8
|
-
*
|
|
9
|
-
* @license MIT
|
|
10
|
-
*/
|
|
11
|
-
|
|
12
|
-
// server.ts
|
|
13
|
-
import { createRequestHandler } from "react-router";
|
|
14
|
-
import { createRequestListener as createRequestListener_ } from "@mjackson/node-fetch-server";
|
|
15
|
-
function createRequestListener(options) {
|
|
16
|
-
let handleRequest = createRequestHandler(options.build, options.mode);
|
|
17
|
-
return createRequestListener_(async (request, client) => {
|
|
18
|
-
let loadContext = await options.getLoadContext?.(request, client);
|
|
19
|
-
return handleRequest(request, loadContext);
|
|
20
|
-
});
|
|
21
|
-
}
|
|
22
|
-
|
|
23
|
-
// sessions/fileStorage.ts
|
|
24
|
-
import { promises as fsp } from "fs";
|
|
25
|
-
import * as path from "path";
|
|
26
|
-
import { createSessionStorage } from "react-router";
|
|
27
|
-
function createFileSessionStorage({
|
|
28
|
-
cookie,
|
|
29
|
-
dir
|
|
30
|
-
}) {
|
|
31
|
-
return createSessionStorage({
|
|
32
|
-
cookie,
|
|
33
|
-
async createData(data, expires) {
|
|
34
|
-
let content = JSON.stringify({ data, expires });
|
|
35
|
-
while (true) {
|
|
36
|
-
let randomBytes = crypto.getRandomValues(new Uint8Array(8));
|
|
37
|
-
let id = Buffer.from(randomBytes).toString("hex");
|
|
38
|
-
try {
|
|
39
|
-
let file = getFile(dir, id);
|
|
40
|
-
if (!file) {
|
|
41
|
-
throw new Error("Error generating session");
|
|
42
|
-
}
|
|
43
|
-
await fsp.mkdir(path.dirname(file), { recursive: true });
|
|
44
|
-
await fsp.writeFile(file, content, { encoding: "utf-8", flag: "wx" });
|
|
45
|
-
return id;
|
|
46
|
-
} catch (error) {
|
|
47
|
-
if (error.code !== "EEXIST") throw error;
|
|
48
|
-
}
|
|
49
|
-
}
|
|
50
|
-
},
|
|
51
|
-
async readData(id) {
|
|
52
|
-
try {
|
|
53
|
-
let file = getFile(dir, id);
|
|
54
|
-
if (!file) {
|
|
55
|
-
return null;
|
|
56
|
-
}
|
|
57
|
-
let content = JSON.parse(await fsp.readFile(file, "utf-8"));
|
|
58
|
-
let data = content.data;
|
|
59
|
-
let expires = typeof content.expires === "string" ? new Date(content.expires) : null;
|
|
60
|
-
if (!expires || expires > /* @__PURE__ */ new Date()) {
|
|
61
|
-
return data;
|
|
62
|
-
}
|
|
63
|
-
if (expires) await fsp.unlink(file);
|
|
64
|
-
return null;
|
|
65
|
-
} catch (error) {
|
|
66
|
-
if (error.code !== "ENOENT") throw error;
|
|
67
|
-
return null;
|
|
68
|
-
}
|
|
69
|
-
},
|
|
70
|
-
async updateData(id, data, expires) {
|
|
71
|
-
let content = JSON.stringify({ data, expires });
|
|
72
|
-
let file = getFile(dir, id);
|
|
73
|
-
if (!file) {
|
|
74
|
-
return;
|
|
75
|
-
}
|
|
76
|
-
await fsp.mkdir(path.dirname(file), { recursive: true });
|
|
77
|
-
await fsp.writeFile(file, content, "utf-8");
|
|
78
|
-
},
|
|
79
|
-
async deleteData(id) {
|
|
80
|
-
if (!id) {
|
|
81
|
-
return;
|
|
82
|
-
}
|
|
83
|
-
let file = getFile(dir, id);
|
|
84
|
-
if (!file) {
|
|
85
|
-
return;
|
|
86
|
-
}
|
|
87
|
-
try {
|
|
88
|
-
await fsp.unlink(file);
|
|
89
|
-
} catch (error) {
|
|
90
|
-
if (error.code !== "ENOENT") throw error;
|
|
91
|
-
}
|
|
92
|
-
}
|
|
93
|
-
});
|
|
94
|
-
}
|
|
95
|
-
function getFile(dir, id) {
|
|
96
|
-
if (!/^[0-9a-f]{16}$/i.test(id)) {
|
|
97
|
-
return null;
|
|
98
|
-
}
|
|
99
|
-
return path.join(dir, id.slice(0, 4), id.slice(4));
|
|
100
|
-
}
|
|
101
|
-
|
|
102
|
-
// stream.ts
|
|
103
|
-
import { Stream } from "stream";
|
|
104
|
-
async function writeReadableStreamToWritable(stream, writable) {
|
|
105
|
-
let reader = stream.getReader();
|
|
106
|
-
let flushable = writable;
|
|
107
|
-
try {
|
|
108
|
-
while (true) {
|
|
109
|
-
let { done, value } = await reader.read();
|
|
110
|
-
if (done) {
|
|
111
|
-
writable.end();
|
|
112
|
-
break;
|
|
113
|
-
}
|
|
114
|
-
writable.write(value);
|
|
115
|
-
if (typeof flushable.flush === "function") {
|
|
116
|
-
flushable.flush();
|
|
117
|
-
}
|
|
118
|
-
}
|
|
119
|
-
} catch (error) {
|
|
120
|
-
writable.destroy(error);
|
|
121
|
-
throw error;
|
|
122
|
-
}
|
|
123
|
-
}
|
|
124
|
-
async function writeAsyncIterableToWritable(iterable, writable) {
|
|
125
|
-
try {
|
|
126
|
-
for await (let chunk of iterable) {
|
|
127
|
-
writable.write(chunk);
|
|
128
|
-
}
|
|
129
|
-
writable.end();
|
|
130
|
-
} catch (error) {
|
|
131
|
-
writable.destroy(error);
|
|
132
|
-
throw error;
|
|
133
|
-
}
|
|
134
|
-
}
|
|
135
|
-
async function readableStreamToString(stream, encoding) {
|
|
136
|
-
let reader = stream.getReader();
|
|
137
|
-
let chunks = [];
|
|
138
|
-
while (true) {
|
|
139
|
-
let { done, value } = await reader.read();
|
|
140
|
-
if (done) {
|
|
141
|
-
break;
|
|
142
|
-
}
|
|
143
|
-
if (value) {
|
|
144
|
-
chunks.push(value);
|
|
145
|
-
}
|
|
146
|
-
}
|
|
147
|
-
return Buffer.concat(chunks).toString(encoding);
|
|
148
|
-
}
|
|
149
|
-
var createReadableStreamFromReadable = (source) => {
|
|
150
|
-
let pump = new StreamPump(source);
|
|
151
|
-
let stream = new ReadableStream(pump, pump);
|
|
152
|
-
return stream;
|
|
153
|
-
};
|
|
154
|
-
var StreamPump = class {
|
|
155
|
-
highWaterMark;
|
|
156
|
-
accumulatedSize;
|
|
157
|
-
stream;
|
|
158
|
-
controller;
|
|
159
|
-
constructor(stream) {
|
|
160
|
-
this.highWaterMark = stream.readableHighWaterMark || new Stream.Readable().readableHighWaterMark;
|
|
161
|
-
this.accumulatedSize = 0;
|
|
162
|
-
this.stream = stream;
|
|
163
|
-
this.enqueue = this.enqueue.bind(this);
|
|
164
|
-
this.error = this.error.bind(this);
|
|
165
|
-
this.close = this.close.bind(this);
|
|
166
|
-
}
|
|
167
|
-
size(chunk) {
|
|
168
|
-
return chunk?.byteLength || 0;
|
|
169
|
-
}
|
|
170
|
-
start(controller) {
|
|
171
|
-
this.controller = controller;
|
|
172
|
-
this.stream.on("data", this.enqueue);
|
|
173
|
-
this.stream.once("error", this.error);
|
|
174
|
-
this.stream.once("end", this.close);
|
|
175
|
-
this.stream.once("close", this.close);
|
|
176
|
-
}
|
|
177
|
-
pull() {
|
|
178
|
-
this.resume();
|
|
179
|
-
}
|
|
180
|
-
cancel(reason) {
|
|
181
|
-
if (this.stream.destroy) {
|
|
182
|
-
this.stream.destroy(reason);
|
|
183
|
-
}
|
|
184
|
-
this.stream.off("data", this.enqueue);
|
|
185
|
-
this.stream.off("error", this.error);
|
|
186
|
-
this.stream.off("end", this.close);
|
|
187
|
-
this.stream.off("close", this.close);
|
|
188
|
-
}
|
|
189
|
-
enqueue(chunk) {
|
|
190
|
-
if (this.controller) {
|
|
191
|
-
try {
|
|
192
|
-
let bytes = chunk instanceof Uint8Array ? chunk : Buffer.from(chunk);
|
|
193
|
-
let available = (this.controller.desiredSize || 0) - bytes.byteLength;
|
|
194
|
-
this.controller.enqueue(bytes);
|
|
195
|
-
if (available <= 0) {
|
|
196
|
-
this.pause();
|
|
197
|
-
}
|
|
198
|
-
} catch (e) {
|
|
199
|
-
this.controller.error(
|
|
200
|
-
new Error(
|
|
201
|
-
"Could not create Buffer, chunk must be of type string or an instance of Buffer, ArrayBuffer, or Array or an Array-like Object"
|
|
202
|
-
)
|
|
203
|
-
);
|
|
204
|
-
this.cancel();
|
|
205
|
-
}
|
|
206
|
-
}
|
|
207
|
-
}
|
|
208
|
-
pause() {
|
|
209
|
-
if (this.stream.pause) {
|
|
210
|
-
this.stream.pause();
|
|
211
|
-
}
|
|
212
|
-
}
|
|
213
|
-
resume() {
|
|
214
|
-
if (this.stream.readable && this.stream.resume) {
|
|
215
|
-
this.stream.resume();
|
|
216
|
-
}
|
|
217
|
-
}
|
|
218
|
-
close() {
|
|
219
|
-
if (this.controller) {
|
|
220
|
-
this.controller.close();
|
|
221
|
-
delete this.controller;
|
|
222
|
-
}
|
|
223
|
-
}
|
|
224
|
-
error(error) {
|
|
225
|
-
if (this.controller) {
|
|
226
|
-
this.controller.error(error);
|
|
227
|
-
delete this.controller;
|
|
228
|
-
}
|
|
229
|
-
}
|
|
230
|
-
};
|
|
231
|
-
export {
|
|
232
|
-
createFileSessionStorage,
|
|
233
|
-
createReadableStreamFromReadable,
|
|
234
|
-
createRequestListener,
|
|
235
|
-
readableStreamToString,
|
|
236
|
-
writeAsyncIterableToWritable,
|
|
237
|
-
writeReadableStreamToWritable
|
|
238
|
-
};
|