You're Invited:Meet the Socket Team at RSAC and BSidesSF 2026, March 23–26.RSVP
Socket
Book a DemoSign in
Socket

@react-router/node

Package Overview
Dependencies
Maintainers
1
Versions
684
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

@react-router/node - npm Package Compare versions

Comparing version
0.0.0-nightly-183fdb88c-20240730
to
0.0.0-nightly-18bac808f-20260323
+48
dist/index.d.mts
import { RequestListener } from 'node:http';
import { ServerBuild, UNSAFE_MiddlewareEnabled, RouterContextProvider, AppLoadContext, SessionData, SessionIdStorageStrategy, SessionStorage } from 'react-router';
import { ClientAddress } from '@mjackson/node-fetch-server';
import { Readable, Writable } from 'node:stream';
type MaybePromise<T> = T | Promise<T>;
interface RequestListenerOptions {
build: ServerBuild | (() => ServerBuild | Promise<ServerBuild>);
getLoadContext?: (request: Request, client: ClientAddress) => UNSAFE_MiddlewareEnabled extends true ? MaybePromise<RouterContextProvider> : MaybePromise<AppLoadContext>;
mode?: string;
}
/**
* Creates a request listener that handles requests using Node's built-in HTTP server.
*
* @param options Options for creating a request listener.
* @returns A request listener that can be used with `http.createServer`.
*/
declare function createRequestListener(options: RequestListenerOptions): RequestListener;
interface FileSessionStorageOptions {
/**
* The Cookie used to store the session id on the client, or options used
* to automatically create one.
*/
cookie?: SessionIdStorageStrategy["cookie"];
/**
* The directory to use to store session files.
*/
dir: string;
}
/**
* Creates a SessionStorage that stores session data on a filesystem.
*
* The advantage of using this instead of cookie session storage is that
* files may contain much more data than cookies.
*
* @see https://api.reactrouter.com/v7/functions/_react-router_node.createFileSessionStorage
*/
declare function createFileSessionStorage<Data = SessionData, FlashData = Data>({ cookie, dir, }: FileSessionStorageOptions): SessionStorage<Data, FlashData>;
declare function writeReadableStreamToWritable(stream: ReadableStream, writable: Writable): Promise<void>;
declare function writeAsyncIterableToWritable(iterable: AsyncIterable<Uint8Array>, writable: Writable): Promise<void>;
declare function readableStreamToString(stream: ReadableStream<Uint8Array>, encoding?: BufferEncoding): Promise<string>;
declare const createReadableStreamFromReadable: (source: Readable & {
readableHighWaterMark?: number;
}) => ReadableStream<Uint8Array>;
export { type RequestListenerOptions, createFileSessionStorage, createReadableStreamFromReadable, createRequestListener, readableStreamToString, writeAsyncIterableToWritable, writeReadableStreamToWritable };
+322
-8

@@ -1,3 +0,324 @@

# `@remix-run/node`
# `@react-router/node`
## 7.13.1
### Patch Changes
- Updated dependencies:
- `react-router@7.13.1`
## 7.13.0
### Patch Changes
- Updated dependencies:
- `react-router@7.13.0`
## 7.12.0
### Patch Changes
- Updated dependencies:
- `react-router@7.12.0`
## 7.11.0
### Patch Changes
- Updated dependencies:
- `react-router@7.11.0`
## 7.10.1
### Patch Changes
- Updated dependencies:
- `react-router@7.10.1`
## 7.10.0
### Patch Changes
- Updated dependencies:
- `react-router@7.10.0`
## 7.9.6
### Patch Changes
- Updated dependencies:
- `react-router@7.9.6`
## 7.9.5
### Patch Changes
- Updated dependencies:
- `react-router@7.9.5`
## 7.9.4
### Patch Changes
- Validate format of incoming session ids in `createFileSessionStorage` ([#14426](https://github.com/remix-run/react-router/pull/14426))
- Updated dependencies:
- `react-router@7.9.4`
## 7.9.3
### Patch Changes
- Updated dependencies:
- `react-router@7.9.3`
## 7.9.2
### Patch Changes
- Updated dependencies:
- `react-router@7.9.2`
## 7.9.1
### Patch Changes
- Updated dependencies:
- `react-router@7.9.1`
## 7.9.0
### Minor Changes
- Stabilize middleware and context APIs. ([#14215](https://github.com/remix-run/react-router/pull/14215))
We have removed the `unstable_` prefix from the following APIs and they are now considered stable and ready for production use:
- [`RouterContextProvider`](https://reactrouter.com/api/utils/RouterContextProvider)
- [`createContext`](https://reactrouter.com/api/utils/createContext)
- `createBrowserRouter` [`getContext`](https://reactrouter.com/api/data-routers/createBrowserRouter#optsgetcontext) option
- `<HydratedRouter>` [`getContext`](https://reactrouter.com/api/framework-routers/HydratedRouter#getcontext) prop
Please see the [Middleware Docs](https://reactrouter.com/how-to/middleware), the [Middleware RFC](https://github.com/remix-run/remix/discussions/7642), and the [Client-side Context RFC](https://github.com/remix-run/react-router/discussions/9856) for more information.
### Patch Changes
- Updated dependencies:
- `react-router@7.9.0`
## 7.8.2
### Patch Changes
- Updated dependencies:
- `react-router@7.8.2`
## 7.8.1
### Patch Changes
- Updated dependencies:
- `react-router@7.8.1`
## 7.8.0
### Patch Changes
- \[UNSTABLE] Change `getLoadContext` signature (`type GetLoadContextFunction`) when `future.unstable_middleware` is enabled so that it returns an `unstable_RouterContextProvider` instance instead of a `Map` used to contruct the instance internally ([#14097](https://github.com/remix-run/react-router/pull/14097))
- This also removes the `type unstable_InitialContext` export
- ⚠️ This is a breaking change if you have adopted middleware and are using a custom server with a `getLoadContext` function
- Updated dependencies:
- `react-router@7.8.0`
## 7.7.1
### Patch Changes
- Updated dependencies:
- `react-router@7.7.1`
## 7.7.0
### Patch Changes
- Updated dependencies:
- `react-router@7.7.0`
## 7.6.3
### Patch Changes
- Remove old "install" package exports ([#13762](https://github.com/remix-run/react-router/pull/13762))
- Updated dependencies:
- `react-router@7.6.3`
## 7.6.2
### Patch Changes
- Updated dependencies:
- `react-router@7.6.2`
## 7.6.1
### Patch Changes
- Updated dependencies:
- `react-router@7.6.1`
## 7.6.0
### Patch Changes
- Updated dependencies:
- `react-router@7.6.0`
## 7.5.3
### Patch Changes
- Updated dependencies:
- `react-router@7.5.3`
## 7.5.2
### Patch Changes
- Updated dependencies:
- `react-router@7.5.2`
## 7.5.1
### Patch Changes
- Updated dependencies:
- `react-router@7.5.1`
## 7.5.0
### Patch Changes
- Updated dependencies:
- `react-router@7.5.0`
## 7.4.1
### Patch Changes
- Updated dependencies:
- `react-router@7.4.1`
## 7.4.0
### Patch Changes
- Updated dependencies:
- `react-router@7.4.0`
## 7.3.0
### Patch Changes
- Updated dependencies:
- `react-router@7.3.0`
## 7.2.0
### Patch Changes
- Updated dependencies:
- `react-router@7.2.0`
## 7.1.5
### Patch Changes
- Updated dependencies:
- `react-router@7.1.5`
## 7.1.4
### Patch Changes
- Updated dependencies:
- `react-router@7.1.4`
## 7.1.3
### Patch Changes
- Updated dependencies:
- `react-router@7.1.3`
## 7.1.2
### Patch Changes
- Updated dependencies:
- `react-router@7.1.2`
## 7.1.1
### Patch Changes
- Updated dependencies:
- `react-router@7.1.1`
## 7.1.0
### Patch Changes
- Updated dependencies:
- `react-router@7.1.0`
## 7.0.2
### Patch Changes
- Updated dependencies:
- `react-router@7.0.2`
## 7.0.1
### Patch Changes
- Updated dependencies:
- `react-router@7.0.1`
## 7.0.0
### Major Changes
- Remove single fetch future flag. ([#11522](https://github.com/remix-run/react-router/pull/11522))
- For Remix consumers migrating to React Router, the `crypto` global from the [Web Crypto API](https://developer.mozilla.org/en-US/docs/Web/API/Web_Crypto_API) is now required when using cookie and session APIs. This means that the following APIs are provided from `react-router` rather than platform-specific packages: ([#11837](https://github.com/remix-run/react-router/pull/11837))
- `createCookie`
- `createCookieSessionStorage`
- `createMemorySessionStorage`
- `createSessionStorage`
For consumers running older versions of Node, the `installGlobals` function from `@remix-run/node` has been updated to define `globalThis.crypto`, using [Node's `require('node:crypto').webcrypto` implementation.](https://nodejs.org/api/webcrypto.html)
Since platform-specific packages no longer need to implement this API, the following low-level APIs have been removed:
- `createCookieFactory`
- `createSessionStorageFactory`
- `createCookieSessionStorageFactory`
- `createMemorySessionStorageFactory`
- update minimum node version to 18 ([#11690](https://github.com/remix-run/react-router/pull/11690))
- Add `exports` field to all packages ([#11675](https://github.com/remix-run/react-router/pull/11675))
- node package no longer re-exports from react-router ([#11702](https://github.com/remix-run/react-router/pull/11702))
- Drop support for Node 18, update minimum Node vestion to 20 ([#12171](https://github.com/remix-run/react-router/pull/12171))
- Remove `installGlobals()` as this should no longer be necessary
### Patch Changes
- Add createRequestListener to @react-router/node ([#12319](https://github.com/remix-run/react-router/pull/12319))
- Remove unstable upload handler. ([#12015](https://github.com/remix-run/react-router/pull/12015))
- Remove unneeded dependency on @web3-storage/multipart-parser ([#12274](https://github.com/remix-run/react-router/pull/12274))
- Updated dependencies:
- `react-router@7.0.0`
## 2.9.0

@@ -139,3 +460,2 @@

- Removed/adjusted types to prefer `unknown` over `any` and to align with underlying React Router types ([#7319](https://github.com/remix-run/remix/pull/7319), [#7354](https://github.com/remix-run/remix/pull/7354)):
- Renamed the `useMatches()` return type from `RouteMatch` to `UIMatch`

@@ -152,7 +472,5 @@ - Renamed `LoaderArgs`/`ActionArgs` to `LoaderFunctionArgs`/`ActionFunctionArgs`

- The route `meta` API now defaults to the new "V2 Meta" API ([#6958](https://github.com/remix-run/remix/pull/6958))
- Please refer to the ([docs](https://remix.run/docs/en/2.0.0/route/meta) and [Preparing for V2](https://remix.run/docs/en/2.0.0/start/v2#route-meta) guide for more information.
- For preparation of using Node's built in fetch implementation, installing the fetch globals is now a responsibility of the app server ([#7009](https://github.com/remix-run/remix/pull/7009))
- If you are using `remix-serve`, nothing is required

@@ -168,3 +486,2 @@ - If you are using your own app server, you will need to install the globals yourself

- `source-map-support` is now a responsibility of the app server ([#7009](https://github.com/remix-run/remix/pull/7009))
- If you are using `remix-serve`, nothing is required

@@ -303,3 +620,2 @@ - If you are using your own app server, you will need to install [`source-map-support`](https://www.npmjs.com/package/source-map-support) yourself.

- We have made a few changes to the API for route module `meta` functions when using the `future.v2_meta` flag. **These changes are _only_ breaking for users who have opted in.** ([#5746](https://github.com/remix-run/remix/pull/5746))
- `V2_HtmlMetaDescriptor` has been renamed to `V2_MetaDescriptor`

@@ -397,3 +713,2 @@ - The `meta` function's arguments have been simplified

Informational Resources:
- <https://gist.github.com/jacob-ebey/9bde9546c1aafaa6bc8c242054b1be26>

@@ -403,3 +718,2 @@ - <https://github.com/remix-run/remix/blob/main/decisions/0004-streaming-apis.md>

Documentation Resources (better docs specific to Remix are in the works):
- <https://reactrouter.com/en/main/utils/defer>

@@ -406,0 +720,0 @@ - <https://reactrouter.com/en/main/components/await>

@@ -1,5 +0,48 @@

export { installGlobals } from "./globals";
export { createFileSessionStorage } from "./sessions/fileStorage";
export { createFileUploadHandler as unstable_createFileUploadHandler, NodeOnDiskFile, } from "./upload/fileUploadHandler";
export { createCookie, createCookieSessionStorage, createMemorySessionStorage, createSessionStorage, } from "./implementations";
export { createReadableStreamFromReadable, readableStreamToString, writeAsyncIterableToWritable, writeReadableStreamToWritable, } from "./stream";
import { RequestListener } from 'node:http';
import { ServerBuild, UNSAFE_MiddlewareEnabled, RouterContextProvider, AppLoadContext, SessionData, SessionIdStorageStrategy, SessionStorage } from 'react-router';
import { ClientAddress } from '@mjackson/node-fetch-server';
import { Readable, Writable } from 'node:stream';
type MaybePromise<T> = T | Promise<T>;
interface RequestListenerOptions {
build: ServerBuild | (() => ServerBuild | Promise<ServerBuild>);
getLoadContext?: (request: Request, client: ClientAddress) => UNSAFE_MiddlewareEnabled extends true ? MaybePromise<RouterContextProvider> : MaybePromise<AppLoadContext>;
mode?: string;
}
/**
* Creates a request listener that handles requests using Node's built-in HTTP server.
*
* @param options Options for creating a request listener.
* @returns A request listener that can be used with `http.createServer`.
*/
declare function createRequestListener(options: RequestListenerOptions): RequestListener;
interface FileSessionStorageOptions {
/**
* The Cookie used to store the session id on the client, or options used
* to automatically create one.
*/
cookie?: SessionIdStorageStrategy["cookie"];
/**
* The directory to use to store session files.
*/
dir: string;
}
/**
* Creates a SessionStorage that stores session data on a filesystem.
*
* The advantage of using this instead of cookie session storage is that
* files may contain much more data than cookies.
*
* @see https://api.reactrouter.com/v7/functions/_react-router_node.createFileSessionStorage
*/
declare function createFileSessionStorage<Data = SessionData, FlashData = Data>({ cookie, dir, }: FileSessionStorageOptions): SessionStorage<Data, FlashData>;
declare function writeReadableStreamToWritable(stream: ReadableStream, writable: Writable): Promise<void>;
declare function writeAsyncIterableToWritable(iterable: AsyncIterable<Uint8Array>, writable: Writable): Promise<void>;
declare function readableStreamToString(stream: ReadableStream<Uint8Array>, encoding?: BufferEncoding): Promise<string>;
declare const createReadableStreamFromReadable: (source: Readable & {
readableHighWaterMark?: number;
}) => ReadableStream<Uint8Array>;
export { type RequestListenerOptions, createFileSessionStorage, createReadableStreamFromReadable, createRequestListener, readableStreamToString, writeAsyncIterableToWritable, writeReadableStreamToWritable };
/**
* @react-router/node v0.0.0-nightly-183fdb88c-20240730
* @react-router/node v0.0.0-nightly-18bac808f-20260323
*

@@ -11,25 +11,270 @@ * Copyright (c) Remix Software Inc.

*/
'use strict';
"use strict";
var __create = Object.create;
var __defProp = Object.defineProperty;
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
var __getOwnPropNames = Object.getOwnPropertyNames;
var __getProtoOf = Object.getPrototypeOf;
var __hasOwnProp = Object.prototype.hasOwnProperty;
var __export = (target, all) => {
for (var name in all)
__defProp(target, name, { get: all[name], enumerable: true });
};
var __copyProps = (to, from, except, desc) => {
if (from && typeof from === "object" || typeof from === "function") {
for (let key of __getOwnPropNames(from))
if (!__hasOwnProp.call(to, key) && key !== except)
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
}
return to;
};
var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
// If the importer is in node compatibility mode or this is not an ESM
// file that has been converted to a CommonJS file using a Babel-
// compatible transform (i.e. "__esModule" has not been set), then set
// "default" to the CommonJS "module.exports" for node compatibility.
isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
mod
));
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
Object.defineProperty(exports, '__esModule', { value: true });
// index.ts
var index_exports = {};
__export(index_exports, {
createFileSessionStorage: () => createFileSessionStorage,
createReadableStreamFromReadable: () => createReadableStreamFromReadable,
createRequestListener: () => createRequestListener,
readableStreamToString: () => readableStreamToString,
writeAsyncIterableToWritable: () => writeAsyncIterableToWritable,
writeReadableStreamToWritable: () => writeReadableStreamToWritable
});
module.exports = __toCommonJS(index_exports);
var globals = require('./globals.js');
var fileStorage = require('./sessions/fileStorage.js');
var fileUploadHandler = require('./upload/fileUploadHandler.js');
var implementations = require('./implementations.js');
var stream = require('./stream.js');
// server.ts
var import_react_router = require("react-router");
var import_node_fetch_server = require("@mjackson/node-fetch-server");
function createRequestListener(options) {
let handleRequest = (0, import_react_router.createRequestHandler)(options.build, options.mode);
return (0, import_node_fetch_server.createRequestListener)(async (request, client) => {
let loadContext = await options.getLoadContext?.(request, client);
return handleRequest(request, loadContext);
});
}
// sessions/fileStorage.ts
var import_node_fs = require("fs");
var path = __toESM(require("path"));
var import_react_router2 = require("react-router");
function createFileSessionStorage({
cookie,
dir
}) {
return (0, import_react_router2.createSessionStorage)({
cookie,
async createData(data, expires) {
let content = JSON.stringify({ data, expires });
while (true) {
let randomBytes = crypto.getRandomValues(new Uint8Array(8));
let id = Buffer.from(randomBytes).toString("hex");
try {
let file = getFile(dir, id);
if (!file) {
throw new Error("Error generating session");
}
await import_node_fs.promises.mkdir(path.dirname(file), { recursive: true });
await import_node_fs.promises.writeFile(file, content, { encoding: "utf-8", flag: "wx" });
return id;
} catch (error) {
if (error.code !== "EEXIST") throw error;
}
}
},
async readData(id) {
try {
let file = getFile(dir, id);
if (!file) {
return null;
}
let content = JSON.parse(await import_node_fs.promises.readFile(file, "utf-8"));
let data = content.data;
let expires = typeof content.expires === "string" ? new Date(content.expires) : null;
if (!expires || expires > /* @__PURE__ */ new Date()) {
return data;
}
if (expires) await import_node_fs.promises.unlink(file);
return null;
} catch (error) {
if (error.code !== "ENOENT") throw error;
return null;
}
},
async updateData(id, data, expires) {
let content = JSON.stringify({ data, expires });
let file = getFile(dir, id);
if (!file) {
return;
}
await import_node_fs.promises.mkdir(path.dirname(file), { recursive: true });
await import_node_fs.promises.writeFile(file, content, "utf-8");
},
async deleteData(id) {
if (!id) {
return;
}
let file = getFile(dir, id);
if (!file) {
return;
}
try {
await import_node_fs.promises.unlink(file);
} catch (error) {
if (error.code !== "ENOENT") throw error;
}
}
});
}
function getFile(dir, id) {
if (!/^[0-9a-f]{16}$/i.test(id)) {
return null;
}
return path.join(dir, id.slice(0, 4), id.slice(4));
}
exports.installGlobals = globals.installGlobals;
exports.createFileSessionStorage = fileStorage.createFileSessionStorage;
exports.NodeOnDiskFile = fileUploadHandler.NodeOnDiskFile;
exports.unstable_createFileUploadHandler = fileUploadHandler.createFileUploadHandler;
exports.createCookie = implementations.createCookie;
exports.createCookieSessionStorage = implementations.createCookieSessionStorage;
exports.createMemorySessionStorage = implementations.createMemorySessionStorage;
exports.createSessionStorage = implementations.createSessionStorage;
exports.createReadableStreamFromReadable = stream.createReadableStreamFromReadable;
exports.readableStreamToString = stream.readableStreamToString;
exports.writeAsyncIterableToWritable = stream.writeAsyncIterableToWritable;
exports.writeReadableStreamToWritable = stream.writeReadableStreamToWritable;
// stream.ts
var import_node_stream = require("stream");
async function writeReadableStreamToWritable(stream, writable) {
let reader = stream.getReader();
let flushable = writable;
try {
while (true) {
let { done, value } = await reader.read();
if (done) {
writable.end();
break;
}
writable.write(value);
if (typeof flushable.flush === "function") {
flushable.flush();
}
}
} catch (error) {
writable.destroy(error);
throw error;
}
}
async function writeAsyncIterableToWritable(iterable, writable) {
try {
for await (let chunk of iterable) {
writable.write(chunk);
}
writable.end();
} catch (error) {
writable.destroy(error);
throw error;
}
}
async function readableStreamToString(stream, encoding) {
let reader = stream.getReader();
let chunks = [];
while (true) {
let { done, value } = await reader.read();
if (done) {
break;
}
if (value) {
chunks.push(value);
}
}
return Buffer.concat(chunks).toString(encoding);
}
var createReadableStreamFromReadable = (source) => {
let pump = new StreamPump(source);
let stream = new ReadableStream(pump, pump);
return stream;
};
var StreamPump = class {
highWaterMark;
accumulatedSize;
stream;
controller;
constructor(stream) {
this.highWaterMark = stream.readableHighWaterMark || new import_node_stream.Stream.Readable().readableHighWaterMark;
this.accumulatedSize = 0;
this.stream = stream;
this.enqueue = this.enqueue.bind(this);
this.error = this.error.bind(this);
this.close = this.close.bind(this);
}
size(chunk) {
return chunk?.byteLength || 0;
}
start(controller) {
this.controller = controller;
this.stream.on("data", this.enqueue);
this.stream.once("error", this.error);
this.stream.once("end", this.close);
this.stream.once("close", this.close);
}
pull() {
this.resume();
}
cancel(reason) {
if (this.stream.destroy) {
this.stream.destroy(reason);
}
this.stream.off("data", this.enqueue);
this.stream.off("error", this.error);
this.stream.off("end", this.close);
this.stream.off("close", this.close);
}
enqueue(chunk) {
if (this.controller) {
try {
let bytes = chunk instanceof Uint8Array ? chunk : Buffer.from(chunk);
let available = (this.controller.desiredSize || 0) - bytes.byteLength;
this.controller.enqueue(bytes);
if (available <= 0) {
this.pause();
}
} catch (error) {
this.controller.error(
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"
)
);
this.cancel();
}
}
}
pause() {
if (this.stream.pause) {
this.stream.pause();
}
}
resume() {
if (this.stream.readable && this.stream.resume) {
this.stream.resume();
}
}
close() {
if (this.controller) {
this.controller.close();
delete this.controller;
}
}
error(error) {
if (this.controller) {
this.controller.error(error);
delete this.controller;
}
}
};
// Annotate the CommonJS export names for ESM import in node:
0 && (module.exports = {
createFileSessionStorage,
createReadableStreamFromReadable,
createRequestListener,
readableStreamToString,
writeAsyncIterableToWritable,
writeReadableStreamToWritable
});
/**
* @react-router/node v0.0.0-nightly-183fdb88c-20240730
* @react-router/node v0.0.0-nightly-18bac808f-20260323
*

@@ -11,6 +11,229 @@ * Copyright (c) Remix Software Inc.

*/
export { installGlobals } from './globals.mjs';
export { createFileSessionStorage } from './sessions/fileStorage.mjs';
export { NodeOnDiskFile, createFileUploadHandler as unstable_createFileUploadHandler } from './upload/fileUploadHandler.mjs';
export { createCookie, createCookieSessionStorage, createMemorySessionStorage, createSessionStorage } from './implementations.mjs';
export { createReadableStreamFromReadable, readableStreamToString, writeAsyncIterableToWritable, writeReadableStreamToWritable } from './stream.mjs';
// server.ts
import { createRequestHandler } from "react-router";
import { createRequestListener as createRequestListener_ } from "@mjackson/node-fetch-server";
function createRequestListener(options) {
let handleRequest = createRequestHandler(options.build, options.mode);
return createRequestListener_(async (request, client) => {
let loadContext = await options.getLoadContext?.(request, client);
return handleRequest(request, loadContext);
});
}
// sessions/fileStorage.ts
import { promises as fsp } from "fs";
import * as path from "path";
import { createSessionStorage } from "react-router";
function createFileSessionStorage({
cookie,
dir
}) {
return createSessionStorage({
cookie,
async createData(data, expires) {
let content = JSON.stringify({ data, expires });
while (true) {
let randomBytes = crypto.getRandomValues(new Uint8Array(8));
let id = Buffer.from(randomBytes).toString("hex");
try {
let file = getFile(dir, id);
if (!file) {
throw new Error("Error generating session");
}
await fsp.mkdir(path.dirname(file), { recursive: true });
await fsp.writeFile(file, content, { encoding: "utf-8", flag: "wx" });
return id;
} catch (error) {
if (error.code !== "EEXIST") throw error;
}
}
},
async readData(id) {
try {
let file = getFile(dir, id);
if (!file) {
return null;
}
let content = JSON.parse(await fsp.readFile(file, "utf-8"));
let data = content.data;
let expires = typeof content.expires === "string" ? new Date(content.expires) : null;
if (!expires || expires > /* @__PURE__ */ new Date()) {
return data;
}
if (expires) await fsp.unlink(file);
return null;
} catch (error) {
if (error.code !== "ENOENT") throw error;
return null;
}
},
async updateData(id, data, expires) {
let content = JSON.stringify({ data, expires });
let file = getFile(dir, id);
if (!file) {
return;
}
await fsp.mkdir(path.dirname(file), { recursive: true });
await fsp.writeFile(file, content, "utf-8");
},
async deleteData(id) {
if (!id) {
return;
}
let file = getFile(dir, id);
if (!file) {
return;
}
try {
await fsp.unlink(file);
} catch (error) {
if (error.code !== "ENOENT") throw error;
}
}
});
}
function getFile(dir, id) {
if (!/^[0-9a-f]{16}$/i.test(id)) {
return null;
}
return path.join(dir, id.slice(0, 4), id.slice(4));
}
// stream.ts
import { Stream } from "stream";
async function writeReadableStreamToWritable(stream, writable) {
let reader = stream.getReader();
let flushable = writable;
try {
while (true) {
let { done, value } = await reader.read();
if (done) {
writable.end();
break;
}
writable.write(value);
if (typeof flushable.flush === "function") {
flushable.flush();
}
}
} catch (error) {
writable.destroy(error);
throw error;
}
}
async function writeAsyncIterableToWritable(iterable, writable) {
try {
for await (let chunk of iterable) {
writable.write(chunk);
}
writable.end();
} catch (error) {
writable.destroy(error);
throw error;
}
}
async function readableStreamToString(stream, encoding) {
let reader = stream.getReader();
let chunks = [];
while (true) {
let { done, value } = await reader.read();
if (done) {
break;
}
if (value) {
chunks.push(value);
}
}
return Buffer.concat(chunks).toString(encoding);
}
var createReadableStreamFromReadable = (source) => {
let pump = new StreamPump(source);
let stream = new ReadableStream(pump, pump);
return stream;
};
var StreamPump = class {
highWaterMark;
accumulatedSize;
stream;
controller;
constructor(stream) {
this.highWaterMark = stream.readableHighWaterMark || new Stream.Readable().readableHighWaterMark;
this.accumulatedSize = 0;
this.stream = stream;
this.enqueue = this.enqueue.bind(this);
this.error = this.error.bind(this);
this.close = this.close.bind(this);
}
size(chunk) {
return chunk?.byteLength || 0;
}
start(controller) {
this.controller = controller;
this.stream.on("data", this.enqueue);
this.stream.once("error", this.error);
this.stream.once("end", this.close);
this.stream.once("close", this.close);
}
pull() {
this.resume();
}
cancel(reason) {
if (this.stream.destroy) {
this.stream.destroy(reason);
}
this.stream.off("data", this.enqueue);
this.stream.off("error", this.error);
this.stream.off("end", this.close);
this.stream.off("close", this.close);
}
enqueue(chunk) {
if (this.controller) {
try {
let bytes = chunk instanceof Uint8Array ? chunk : Buffer.from(chunk);
let available = (this.controller.desiredSize || 0) - bytes.byteLength;
this.controller.enqueue(bytes);
if (available <= 0) {
this.pause();
}
} catch (error) {
this.controller.error(
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"
)
);
this.cancel();
}
}
}
pause() {
if (this.stream.pause) {
this.stream.pause();
}
}
resume() {
if (this.stream.readable && this.stream.resume) {
this.stream.resume();
}
}
close() {
if (this.controller) {
this.controller.close();
delete this.controller;
}
}
error(error) {
if (this.controller) {
this.controller.error(error);
delete this.controller;
}
}
};
export {
createFileSessionStorage,
createReadableStreamFromReadable,
createRequestListener,
readableStreamToString,
writeAsyncIterableToWritable,
writeReadableStreamToWritable
};
+39
-29
{
"name": "@react-router/node",
"version": "0.0.0-nightly-183fdb88c-20240730",
"version": "0.0.0-nightly-18bac808f-20260323",
"description": "Node.js platform abstractions for React Router",

@@ -14,37 +14,49 @@ "bugs": {

"license": "MIT",
"types": "dist/index.d.ts",
"main": "dist/index.js",
"typings": "dist/index.d.ts",
"exports": {
".": {
"types": "./dist/index.d.ts",
"import": "./dist/index.mjs",
"require": "./dist/index.js"
"node": {
"types": "./dist/index.d.ts",
"module-sync": "./dist/index.mjs",
"default": "./dist/index.js"
},
"import": {
"types": "./dist/index.d.mts",
"default": "./dist/index.mjs"
},
"default": {
"types": "./dist/index.d.ts",
"default": "./dist/index.js"
}
},
"./install": {
"types": "./dist/install.d.ts",
"import": "./dist/install.mjs",
"require": "./dist/install.js"
},
"./package.json": "./package.json"
},
"sideEffects": [
"./dist/install.js",
"./dist/install.mjs"
],
"wireit": {
"build": {
"command": "tsup",
"files": [
"../../pnpm-workspace.yaml",
"sessions/**",
"*.ts",
"tsconfig.json",
"package.json"
],
"output": [
"dist/**"
]
}
},
"dependencies": {
"@web3-storage/multipart-parser": "^1.0.0",
"cookie-signature": "^1.1.0",
"source-map-support": "^0.5.21",
"stream-slice": "^0.1.2",
"undici": "^6.19.2"
"@mjackson/node-fetch-server": "^0.2.0"
},
"devDependencies": {
"@types/cookie-signature": "^1.0.3",
"@types/source-map-support": "^0.5.4",
"typescript": "^5.1.6",
"react-router": "0.0.0-nightly-183fdb88c-20240730"
"tsup": "^8.3.0",
"typescript": "^5.4.5",
"wireit": "0.14.9",
"react-router": "0.0.0-nightly-18bac808f-20260323"
},
"peerDependencies": {
"typescript": "^5.1.0",
"react-router": "0.0.0-nightly-183fdb88c-20240730"
"react-router": "0.0.0-nightly-18bac808f-20260323"
},

@@ -57,9 +69,6 @@ "peerDependenciesMeta": {

"engines": {
"node": ">=18.0.0"
"node": ">=20.0.0"
},
"files": [
"dist/",
"globals.d.ts",
"install.d.ts",
"install.js",
"CHANGELOG.md",

@@ -70,4 +79,5 @@ "LICENSE.md",

"scripts": {
"tsc": "tsc"
"build": "wireit",
"typecheck": "tsc"
}
}

@@ -1,13 +0,5 @@

# Welcome to Remix!
Node.js platform abstractions for React Router
[Remix](https://remix.run) is a web framework that helps you build better websites with React.
To get started, open a new shell and run:
```sh
npx create-remix@latest
npm install @react-router/node
```
Then follow the prompts you see in your terminal.
For more information about Remix, [visit remix.run](https://remix.run)!
import type { SignFunction, UnsignFunction } from "react-router";
export declare const sign: SignFunction;
export declare const unsign: UnsignFunction;
/**
* @react-router/node v0.0.0-nightly-183fdb88c-20240730
*
* Copyright (c) Remix Software Inc.
*
* This source code is licensed under the MIT license found in the
* LICENSE.md file in the root directory of this source tree.
*
* @license MIT
*/
'use strict';
Object.defineProperty(exports, '__esModule', { value: true });
var cookieSignature = require('cookie-signature');
function _interopDefaultLegacy (e) { return e && typeof e === 'object' && 'default' in e ? e : { 'default': e }; }
var cookieSignature__default = /*#__PURE__*/_interopDefaultLegacy(cookieSignature);
const sign = async (value, secret) => {
return cookieSignature__default["default"].sign(value, secret);
};
const unsign = async (signed, secret) => {
return cookieSignature__default["default"].unsign(signed, secret);
};
exports.sign = sign;
exports.unsign = unsign;
/**
* @react-router/node v0.0.0-nightly-183fdb88c-20240730
*
* Copyright (c) Remix Software Inc.
*
* This source code is licensed under the MIT license found in the
* LICENSE.md file in the root directory of this source tree.
*
* @license MIT
*/
import cookieSignature from 'cookie-signature';
const sign = async (value, secret) => {
return cookieSignature.sign(value, secret);
};
const unsign = async (signed, secret) => {
return cookieSignature.unsign(signed, secret);
};
export { sign, unsign };
declare global {
namespace NodeJS {
interface ProcessEnv {
NODE_ENV: "development" | "production" | "test";
}
interface Global {
File: typeof File;
Headers: typeof Headers;
Request: typeof Request;
Response: typeof Response;
fetch: typeof fetch;
FormData: typeof FormData;
ReadableStream: typeof ReadableStream;
WritableStream: typeof WritableStream;
}
}
interface RequestInit {
duplex?: "half";
}
}
export declare function installGlobals(): void;
/**
* @react-router/node v0.0.0-nightly-183fdb88c-20240730
*
* Copyright (c) Remix Software Inc.
*
* This source code is licensed under the MIT license found in the
* LICENSE.md file in the root directory of this source tree.
*
* @license MIT
*/
'use strict';
Object.defineProperty(exports, '__esModule', { value: true });
var undici = require('undici');
function installGlobals() {
global.File = undici.File;
// @ts-ignore - this shows as an error in VSCode but is not an error via TSC so we can't use `ts-expect-error`
global.Headers = undici.Headers;
// @ts-expect-error - overriding globals
global.Request = undici.Request;
// @ts-expect-error - overriding globals
global.Response = undici.Response;
// @ts-expect-error - overriding globals
global.fetch = undici.fetch;
// @ts-expect-error - overriding globals
global.FormData = undici.FormData;
}
exports.installGlobals = installGlobals;
/**
* @react-router/node v0.0.0-nightly-183fdb88c-20240730
*
* Copyright (c) Remix Software Inc.
*
* This source code is licensed under the MIT license found in the
* LICENSE.md file in the root directory of this source tree.
*
* @license MIT
*/
import { File, Headers, Request, Response, fetch, FormData } from 'undici';
function installGlobals() {
global.File = File;
// @ts-ignore - this shows as an error in VSCode but is not an error via TSC so we can't use `ts-expect-error`
global.Headers = Headers;
// @ts-expect-error - overriding globals
global.Request = Request;
// @ts-expect-error - overriding globals
global.Response = Response;
// @ts-expect-error - overriding globals
global.fetch = fetch;
// @ts-expect-error - overriding globals
global.FormData = FormData;
}
export { installGlobals };
export declare const createCookie: import("react-router").CreateCookieFunction;
export declare const createCookieSessionStorage: import("react-router").CreateCookieSessionStorageFunction;
export declare const createSessionStorage: import("react-router").CreateSessionStorageFunction;
export declare const createMemorySessionStorage: import("react-router").CreateMemorySessionStorageFunction;
/**
* @react-router/node v0.0.0-nightly-183fdb88c-20240730
*
* Copyright (c) Remix Software Inc.
*
* This source code is licensed under the MIT license found in the
* LICENSE.md file in the root directory of this source tree.
*
* @license MIT
*/
'use strict';
Object.defineProperty(exports, '__esModule', { value: true });
var reactRouter = require('react-router');
var crypto = require('./crypto.js');
const createCookie = reactRouter.createCookieFactory({
sign: crypto.sign,
unsign: crypto.unsign
});
const createCookieSessionStorage = reactRouter.createCookieSessionStorageFactory(createCookie);
const createSessionStorage = reactRouter.createSessionStorageFactory(createCookie);
const createMemorySessionStorage = reactRouter.createMemorySessionStorageFactory(createSessionStorage);
exports.createCookie = createCookie;
exports.createCookieSessionStorage = createCookieSessionStorage;
exports.createMemorySessionStorage = createMemorySessionStorage;
exports.createSessionStorage = createSessionStorage;
/**
* @react-router/node v0.0.0-nightly-183fdb88c-20240730
*
* Copyright (c) Remix Software Inc.
*
* This source code is licensed under the MIT license found in the
* LICENSE.md file in the root directory of this source tree.
*
* @license MIT
*/
import { createCookieFactory, createCookieSessionStorageFactory, createSessionStorageFactory, createMemorySessionStorageFactory } from 'react-router';
import { sign, unsign } from './crypto.mjs';
const createCookie = createCookieFactory({
sign,
unsign
});
const createCookieSessionStorage = createCookieSessionStorageFactory(createCookie);
const createSessionStorage = createSessionStorageFactory(createCookie);
const createMemorySessionStorage = createMemorySessionStorageFactory(createSessionStorage);
export { createCookie, createCookieSessionStorage, createMemorySessionStorage, createSessionStorage };
export {};
/**
* @react-router/node v0.0.0-nightly-183fdb88c-20240730
*
* Copyright (c) Remix Software Inc.
*
* This source code is licensed under the MIT license found in the
* LICENSE.md file in the root directory of this source tree.
*
* @license MIT
*/
'use strict';
var globals = require('./globals.js');
globals.installGlobals();
/**
* @react-router/node v0.0.0-nightly-183fdb88c-20240730
*
* Copyright (c) Remix Software Inc.
*
* This source code is licensed under the MIT license found in the
* LICENSE.md file in the root directory of this source tree.
*
* @license MIT
*/
import { installGlobals } from './globals.mjs';
installGlobals();
import type { SessionStorage, SessionIdStorageStrategy, SessionData } from "react-router";
interface FileSessionStorageOptions {
/**
* The Cookie used to store the session id on the client, or options used
* to automatically create one.
*/
cookie?: SessionIdStorageStrategy["cookie"];
/**
* The directory to use to store session files.
*/
dir: string;
}
/**
* Creates a SessionStorage that stores session data on a filesystem.
*
* The advantage of using this instead of cookie session storage is that
* files may contain much more data than cookies.
*
* @see https://remix.run/utils/sessions#createfilesessionstorage-node
*/
export declare function createFileSessionStorage<Data = SessionData, FlashData = Data>({ cookie, dir, }: FileSessionStorageOptions): SessionStorage<Data, FlashData>;
export declare function getFile(dir: string, id: string): string;
export {};
/**
* @react-router/node v0.0.0-nightly-183fdb88c-20240730
*
* Copyright (c) Remix Software Inc.
*
* This source code is licensed under the MIT license found in the
* LICENSE.md file in the root directory of this source tree.
*
* @license MIT
*/
'use strict';
Object.defineProperty(exports, '__esModule', { value: true });
var crypto = require('node:crypto');
var node_fs = require('node:fs');
var path = require('node:path');
var implementations = require('../implementations.js');
function _interopNamespace(e) {
if (e && e.__esModule) return e;
var n = Object.create(null);
if (e) {
Object.keys(e).forEach(function (k) {
if (k !== 'default') {
var d = Object.getOwnPropertyDescriptor(e, k);
Object.defineProperty(n, k, d.get ? d : {
enumerable: true,
get: function () { return e[k]; }
});
}
});
}
n["default"] = e;
return Object.freeze(n);
}
var crypto__namespace = /*#__PURE__*/_interopNamespace(crypto);
var path__namespace = /*#__PURE__*/_interopNamespace(path);
/**
* Creates a SessionStorage that stores session data on a filesystem.
*
* The advantage of using this instead of cookie session storage is that
* files may contain much more data than cookies.
*
* @see https://remix.run/utils/sessions#createfilesessionstorage-node
*/
function createFileSessionStorage({
cookie,
dir
}) {
return implementations.createSessionStorage({
cookie,
async createData(data, expires) {
let content = JSON.stringify({
data,
expires
});
while (true) {
// TODO: Once Node v19 is supported we should use the globally provided
// Web Crypto API's crypto.getRandomValues() function here instead.
let randomBytes = crypto__namespace.webcrypto.getRandomValues(new Uint8Array(8));
// This storage manages an id space of 2^64 ids, which is far greater
// than the maximum number of files allowed on an NTFS or ext4 volume
// (2^32). However, the larger id space should help to avoid collisions
// with existing ids when creating new sessions, which speeds things up.
let id = Buffer.from(randomBytes).toString("hex");
try {
let file = getFile(dir, id);
await node_fs.promises.mkdir(path__namespace.dirname(file), {
recursive: true
});
await node_fs.promises.writeFile(file, content, {
encoding: "utf-8",
flag: "wx"
});
return id;
} catch (error) {
if (error.code !== "EEXIST") throw error;
}
}
},
async readData(id) {
try {
let file = getFile(dir, id);
let content = JSON.parse(await node_fs.promises.readFile(file, "utf-8"));
let data = content.data;
let expires = typeof content.expires === "string" ? new Date(content.expires) : null;
if (!expires || expires > new Date()) {
return data;
}
// Remove expired session data.
if (expires) await node_fs.promises.unlink(file);
return null;
} catch (error) {
if (error.code !== "ENOENT") throw error;
return null;
}
},
async updateData(id, data, expires) {
let content = JSON.stringify({
data,
expires
});
let file = getFile(dir, id);
await node_fs.promises.mkdir(path__namespace.dirname(file), {
recursive: true
});
await node_fs.promises.writeFile(file, content, "utf-8");
},
async deleteData(id) {
// Return early if the id is empty, otherwise we'll end up trying to
// unlink the dir, which will cause the EPERM error.
if (!id) {
return;
}
try {
await node_fs.promises.unlink(getFile(dir, id));
} catch (error) {
if (error.code !== "ENOENT") throw error;
}
}
});
}
function getFile(dir, id) {
// Divide the session id up into a directory (first 2 bytes) and filename
// (remaining 6 bytes) to reduce the chance of having very large directories,
// which should speed up file access. This is a maximum of 2^16 directories,
// each with 2^48 files.
return path__namespace.join(dir, id.slice(0, 4), id.slice(4));
}
exports.createFileSessionStorage = createFileSessionStorage;
exports.getFile = getFile;
/**
* @react-router/node v0.0.0-nightly-183fdb88c-20240730
*
* Copyright (c) Remix Software Inc.
*
* This source code is licensed under the MIT license found in the
* LICENSE.md file in the root directory of this source tree.
*
* @license MIT
*/
import * as crypto from 'node:crypto';
import { promises } from 'node:fs';
import * as path from 'node:path';
import { createSessionStorage } from '../implementations.mjs';
/**
* Creates a SessionStorage that stores session data on a filesystem.
*
* The advantage of using this instead of cookie session storage is that
* files may contain much more data than cookies.
*
* @see https://remix.run/utils/sessions#createfilesessionstorage-node
*/
function createFileSessionStorage({
cookie,
dir
}) {
return createSessionStorage({
cookie,
async createData(data, expires) {
let content = JSON.stringify({
data,
expires
});
while (true) {
// TODO: Once Node v19 is supported we should use the globally provided
// Web Crypto API's crypto.getRandomValues() function here instead.
let randomBytes = crypto.webcrypto.getRandomValues(new Uint8Array(8));
// This storage manages an id space of 2^64 ids, which is far greater
// than the maximum number of files allowed on an NTFS or ext4 volume
// (2^32). However, the larger id space should help to avoid collisions
// with existing ids when creating new sessions, which speeds things up.
let id = Buffer.from(randomBytes).toString("hex");
try {
let file = getFile(dir, id);
await promises.mkdir(path.dirname(file), {
recursive: true
});
await promises.writeFile(file, content, {
encoding: "utf-8",
flag: "wx"
});
return id;
} catch (error) {
if (error.code !== "EEXIST") throw error;
}
}
},
async readData(id) {
try {
let file = getFile(dir, id);
let content = JSON.parse(await promises.readFile(file, "utf-8"));
let data = content.data;
let expires = typeof content.expires === "string" ? new Date(content.expires) : null;
if (!expires || expires > new Date()) {
return data;
}
// Remove expired session data.
if (expires) await promises.unlink(file);
return null;
} catch (error) {
if (error.code !== "ENOENT") throw error;
return null;
}
},
async updateData(id, data, expires) {
let content = JSON.stringify({
data,
expires
});
let file = getFile(dir, id);
await promises.mkdir(path.dirname(file), {
recursive: true
});
await promises.writeFile(file, content, "utf-8");
},
async deleteData(id) {
// Return early if the id is empty, otherwise we'll end up trying to
// unlink the dir, which will cause the EPERM error.
if (!id) {
return;
}
try {
await promises.unlink(getFile(dir, id));
} catch (error) {
if (error.code !== "ENOENT") throw error;
}
}
});
}
function getFile(dir, id) {
// Divide the session id up into a directory (first 2 bytes) and filename
// (remaining 6 bytes) to reduce the chance of having very large directories,
// which should speed up file access. This is a maximum of 2^16 directories,
// each with 2^48 files.
return path.join(dir, id.slice(0, 4), id.slice(4));
}
export { createFileSessionStorage, getFile };
/// <reference types="node" />
/// <reference types="node" />
import type { Readable, Writable } from "node:stream";
export declare function writeReadableStreamToWritable(stream: ReadableStream, writable: Writable): Promise<void>;
export declare function writeAsyncIterableToWritable(iterable: AsyncIterable<Uint8Array>, writable: Writable): Promise<void>;
export declare function readableStreamToString(stream: ReadableStream<Uint8Array>, encoding?: BufferEncoding): Promise<string>;
export declare const createReadableStreamFromReadable: (source: Readable & {
readableHighWaterMark?: number;
}) => ReadableStream<Uint8Array>;
/**
* @react-router/node v0.0.0-nightly-183fdb88c-20240730
*
* Copyright (c) Remix Software Inc.
*
* This source code is licensed under the MIT license found in the
* LICENSE.md file in the root directory of this source tree.
*
* @license MIT
*/
'use strict';
Object.defineProperty(exports, '__esModule', { value: true });
var node_stream = require('node:stream');
async function writeReadableStreamToWritable(stream, writable) {
let reader = stream.getReader();
let flushable = writable;
try {
while (true) {
let {
done,
value
} = await reader.read();
if (done) {
writable.end();
break;
}
writable.write(value);
if (typeof flushable.flush === "function") {
flushable.flush();
}
}
} catch (error) {
writable.destroy(error);
throw error;
}
}
async function writeAsyncIterableToWritable(iterable, writable) {
try {
for await (let chunk of iterable) {
writable.write(chunk);
}
writable.end();
} catch (error) {
writable.destroy(error);
throw error;
}
}
async function readableStreamToString(stream, encoding) {
let reader = stream.getReader();
let chunks = [];
while (true) {
let {
done,
value
} = await reader.read();
if (done) {
break;
}
if (value) {
chunks.push(value);
}
}
return Buffer.concat(chunks).toString(encoding);
}
const createReadableStreamFromReadable = source => {
let pump = new StreamPump(source);
let stream = new ReadableStream(pump, pump);
return stream;
};
class StreamPump {
constructor(stream) {
this.highWaterMark = stream.readableHighWaterMark || new node_stream.Stream.Readable().readableHighWaterMark;
this.accumalatedSize = 0;
this.stream = stream;
this.enqueue = this.enqueue.bind(this);
this.error = this.error.bind(this);
this.close = this.close.bind(this);
}
size(chunk) {
return (chunk === null || chunk === void 0 ? void 0 : chunk.byteLength) || 0;
}
start(controller) {
this.controller = controller;
this.stream.on("data", this.enqueue);
this.stream.once("error", this.error);
this.stream.once("end", this.close);
this.stream.once("close", this.close);
}
pull() {
this.resume();
}
cancel(reason) {
if (this.stream.destroy) {
this.stream.destroy(reason);
}
this.stream.off("data", this.enqueue);
this.stream.off("error", this.error);
this.stream.off("end", this.close);
this.stream.off("close", this.close);
}
enqueue(chunk) {
if (this.controller) {
try {
let bytes = chunk instanceof Uint8Array ? chunk : Buffer.from(chunk);
let available = (this.controller.desiredSize || 0) - bytes.byteLength;
this.controller.enqueue(bytes);
if (available <= 0) {
this.pause();
}
} catch (error) {
this.controller.error(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"));
this.cancel();
}
}
}
pause() {
if (this.stream.pause) {
this.stream.pause();
}
}
resume() {
if (this.stream.readable && this.stream.resume) {
this.stream.resume();
}
}
close() {
if (this.controller) {
this.controller.close();
delete this.controller;
}
}
error(error) {
if (this.controller) {
this.controller.error(error);
delete this.controller;
}
}
}
exports.createReadableStreamFromReadable = createReadableStreamFromReadable;
exports.readableStreamToString = readableStreamToString;
exports.writeAsyncIterableToWritable = writeAsyncIterableToWritable;
exports.writeReadableStreamToWritable = writeReadableStreamToWritable;
/**
* @react-router/node v0.0.0-nightly-183fdb88c-20240730
*
* Copyright (c) Remix Software Inc.
*
* This source code is licensed under the MIT license found in the
* LICENSE.md file in the root directory of this source tree.
*
* @license MIT
*/
import { Stream } from 'node:stream';
async function writeReadableStreamToWritable(stream, writable) {
let reader = stream.getReader();
let flushable = writable;
try {
while (true) {
let {
done,
value
} = await reader.read();
if (done) {
writable.end();
break;
}
writable.write(value);
if (typeof flushable.flush === "function") {
flushable.flush();
}
}
} catch (error) {
writable.destroy(error);
throw error;
}
}
async function writeAsyncIterableToWritable(iterable, writable) {
try {
for await (let chunk of iterable) {
writable.write(chunk);
}
writable.end();
} catch (error) {
writable.destroy(error);
throw error;
}
}
async function readableStreamToString(stream, encoding) {
let reader = stream.getReader();
let chunks = [];
while (true) {
let {
done,
value
} = await reader.read();
if (done) {
break;
}
if (value) {
chunks.push(value);
}
}
return Buffer.concat(chunks).toString(encoding);
}
const createReadableStreamFromReadable = source => {
let pump = new StreamPump(source);
let stream = new ReadableStream(pump, pump);
return stream;
};
class StreamPump {
constructor(stream) {
this.highWaterMark = stream.readableHighWaterMark || new Stream.Readable().readableHighWaterMark;
this.accumalatedSize = 0;
this.stream = stream;
this.enqueue = this.enqueue.bind(this);
this.error = this.error.bind(this);
this.close = this.close.bind(this);
}
size(chunk) {
return (chunk === null || chunk === void 0 ? void 0 : chunk.byteLength) || 0;
}
start(controller) {
this.controller = controller;
this.stream.on("data", this.enqueue);
this.stream.once("error", this.error);
this.stream.once("end", this.close);
this.stream.once("close", this.close);
}
pull() {
this.resume();
}
cancel(reason) {
if (this.stream.destroy) {
this.stream.destroy(reason);
}
this.stream.off("data", this.enqueue);
this.stream.off("error", this.error);
this.stream.off("end", this.close);
this.stream.off("close", this.close);
}
enqueue(chunk) {
if (this.controller) {
try {
let bytes = chunk instanceof Uint8Array ? chunk : Buffer.from(chunk);
let available = (this.controller.desiredSize || 0) - bytes.byteLength;
this.controller.enqueue(bytes);
if (available <= 0) {
this.pause();
}
} catch (error) {
this.controller.error(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"));
this.cancel();
}
}
}
pause() {
if (this.stream.pause) {
this.stream.pause();
}
}
resume() {
if (this.stream.readable && this.stream.resume) {
this.stream.resume();
}
}
close() {
if (this.controller) {
this.controller.close();
delete this.controller;
}
}
error(error) {
if (this.controller) {
this.controller.error(error);
delete this.controller;
}
}
}
export { createReadableStreamFromReadable, readableStreamToString, writeAsyncIterableToWritable, writeReadableStreamToWritable };
/// <reference types="node" />
import type { UploadHandler } from "react-router";
export type FileUploadHandlerFilterArgs = {
filename: string;
contentType: string;
name: string;
};
export type FileUploadHandlerPathResolverArgs = {
filename: string;
contentType: string;
name: string;
};
/**
* Chooses the path of the file to be uploaded. If a string is not
* returned the file will not be written.
*/
export type FileUploadHandlerPathResolver = (args: FileUploadHandlerPathResolverArgs) => string | undefined;
export type FileUploadHandlerOptions = {
/**
* Avoid file conflicts by appending a count on the end of the filename
* if it already exists on disk. Defaults to `true`.
*/
avoidFileConflicts?: boolean;
/**
* The directory to write the upload.
*/
directory?: string | FileUploadHandlerPathResolver;
/**
* The name of the file in the directory. Can be a relative path, the directory
* structure will be created if it does not exist.
*/
file?: FileUploadHandlerPathResolver;
/**
* The maximum upload size allowed. If the size is exceeded an error will be thrown.
* Defaults to 3000000B (3MB).
*/
maxPartSize?: number;
/**
*
* @param filename
* @param contentType
* @param name
*/
filter?(args: FileUploadHandlerFilterArgs): boolean | Promise<boolean>;
};
export declare function createFileUploadHandler({ directory, avoidFileConflicts, file, filter, maxPartSize, }?: FileUploadHandlerOptions): UploadHandler;
export declare class NodeOnDiskFile implements Omit<File, "constructor"> {
private filepath;
type: string;
private slicer?;
name: string;
lastModified: number;
webkitRelativePath: string;
prototype: File;
constructor(filepath: string, type: string, slicer?: {
start: number;
end: number;
} | undefined);
get size(): number;
slice(start?: number, end?: number, type?: string): Blob;
arrayBuffer(): Promise<ArrayBuffer>;
stream(): ReadableStream<any>;
stream(): NodeJS.ReadableStream;
text(): Promise<string>;
get [Symbol.toStringTag](): string;
remove(): Promise<void>;
getFilePath(): string;
}
/**
* @react-router/node v0.0.0-nightly-183fdb88c-20240730
*
* Copyright (c) Remix Software Inc.
*
* This source code is licensed under the MIT license found in the
* LICENSE.md file in the root directory of this source tree.
*
* @license MIT
*/
'use strict';
Object.defineProperty(exports, '__esModule', { value: true });
var crypto = require('node:crypto');
var node_fs = require('node:fs');
var promises = require('node:fs/promises');
var node_os = require('node:os');
var path = require('node:path');
var node_stream = require('node:stream');
var node_util = require('node:util');
var reactRouter = require('react-router');
var streamSlice = require('stream-slice');
var stream = require('../stream.js');
function _interopNamespace(e) {
if (e && e.__esModule) return e;
var n = Object.create(null);
if (e) {
Object.keys(e).forEach(function (k) {
if (k !== 'default') {
var d = Object.getOwnPropertyDescriptor(e, k);
Object.defineProperty(n, k, d.get ? d : {
enumerable: true,
get: function () { return e[k]; }
});
}
});
}
n["default"] = e;
return Object.freeze(n);
}
var streamSlice__namespace = /*#__PURE__*/_interopNamespace(streamSlice);
/**
* Chooses the path of the file to be uploaded. If a string is not
* returned the file will not be written.
*/
let defaultFilePathResolver = ({
filename
}) => {
let ext = filename ? path.extname(filename) : "";
return "upload_" + crypto.randomBytes(4).readUInt32LE(0) + ext;
};
async function uniqueFile(filepath) {
let ext = path.extname(filepath);
let uniqueFilepath = filepath;
for (let i = 1; await promises.stat(uniqueFilepath).then(() => true).catch(() => false); i++) {
uniqueFilepath = (ext ? filepath.slice(0, -ext.length) : filepath) + `-${new Date().getTime()}${ext}`;
}
return uniqueFilepath;
}
function createFileUploadHandler({
directory = node_os.tmpdir(),
avoidFileConflicts = true,
file = defaultFilePathResolver,
filter,
maxPartSize = 3000000
} = {}) {
return async ({
name,
filename,
contentType,
data
}) => {
if (!filename || filter && !(await filter({
name,
filename,
contentType
}))) {
return undefined;
}
let dir = typeof directory === "string" ? directory : directory({
name,
filename,
contentType
});
if (!dir) {
return undefined;
}
let filedir = path.resolve(dir);
let path$1 = typeof file === "string" ? file : file({
name,
filename,
contentType
});
if (!path$1) {
return undefined;
}
let filepath = path.resolve(filedir, path$1);
if (avoidFileConflicts) {
filepath = await uniqueFile(filepath);
}
await promises.mkdir(path.dirname(filepath), {
recursive: true
}).catch(() => {});
let writeFileStream = node_fs.createWriteStream(filepath);
let size = 0;
let deleteFile = false;
try {
for await (let chunk of data) {
size += chunk.byteLength;
if (size > maxPartSize) {
deleteFile = true;
throw new reactRouter.MaxPartSizeExceededError(name, maxPartSize);
}
writeFileStream.write(chunk);
}
} finally {
writeFileStream.end();
await node_util.promisify(node_stream.finished)(writeFileStream);
if (deleteFile) {
await promises.rm(filepath).catch(() => {});
}
}
// TODO: remove this typecast once TS fixed File class regression
// https://github.com/microsoft/TypeScript/issues/52166
return new NodeOnDiskFile(filepath, contentType);
};
}
// TODO: remove this `Omit` usage once TS fixed File class regression
// https://github.com/microsoft/TypeScript/issues/52166
class NodeOnDiskFile {
lastModified = 0;
webkitRelativePath = "";
// TODO: remove this property once TS fixed File class regression
// https://github.com/microsoft/TypeScript/issues/52166
prototype = File.prototype;
constructor(filepath, type, slicer) {
this.filepath = filepath;
this.type = type;
this.slicer = slicer;
this.name = path.basename(filepath);
}
get size() {
let stats = node_fs.statSync(this.filepath);
if (this.slicer) {
let slice = this.slicer.end - this.slicer.start;
return slice < 0 ? 0 : slice > stats.size ? stats.size : slice;
}
return stats.size;
}
slice(start, end, type) {
var _this$slicer;
if (typeof start === "number" && start < 0) start = this.size + start;
if (typeof end === "number" && end < 0) end = this.size + end;
let startOffset = ((_this$slicer = this.slicer) === null || _this$slicer === void 0 ? void 0 : _this$slicer.start) || 0;
start = startOffset + (start || 0);
end = startOffset + (end || this.size);
return new NodeOnDiskFile(this.filepath, typeof type === "string" ? type : this.type, {
start,
end
}
// TODO: remove this typecast once TS fixed File class regression
// https://github.com/microsoft/TypeScript/issues/52166
);
}
async arrayBuffer() {
let stream = node_fs.createReadStream(this.filepath);
if (this.slicer) {
stream = stream.pipe(streamSlice__namespace.slice(this.slicer.start, this.slicer.end));
}
return new Promise((resolve, reject) => {
let buf = [];
stream.on("data", chunk => buf.push(chunk));
stream.on("end", () => resolve(Buffer.concat(buf)));
stream.on("error", err => reject(err));
});
}
stream() {
let stream$1 = node_fs.createReadStream(this.filepath);
if (this.slicer) {
stream$1 = stream$1.pipe(streamSlice__namespace.slice(this.slicer.start, this.slicer.end));
}
return stream.createReadableStreamFromReadable(stream$1);
}
async text() {
return stream.readableStreamToString(this.stream());
}
get [Symbol.toStringTag]() {
return "File";
}
remove() {
return promises.unlink(this.filepath);
}
getFilePath() {
return this.filepath;
}
}
exports.NodeOnDiskFile = NodeOnDiskFile;
exports.createFileUploadHandler = createFileUploadHandler;
/**
* @react-router/node v0.0.0-nightly-183fdb88c-20240730
*
* Copyright (c) Remix Software Inc.
*
* This source code is licensed under the MIT license found in the
* LICENSE.md file in the root directory of this source tree.
*
* @license MIT
*/
import { randomBytes } from 'node:crypto';
import { createWriteStream, statSync, createReadStream } from 'node:fs';
import { mkdir, rm, unlink, stat } from 'node:fs/promises';
import { tmpdir } from 'node:os';
import { resolve, dirname, basename, extname } from 'node:path';
import { finished } from 'node:stream';
import { promisify } from 'node:util';
import { MaxPartSizeExceededError } from 'react-router';
import * as streamSlice from 'stream-slice';
import { createReadableStreamFromReadable, readableStreamToString } from '../stream.mjs';
let defaultFilePathResolver = ({
filename
}) => {
let ext = filename ? extname(filename) : "";
return "upload_" + randomBytes(4).readUInt32LE(0) + ext;
};
async function uniqueFile(filepath) {
let ext = extname(filepath);
let uniqueFilepath = filepath;
for (let i = 1; await stat(uniqueFilepath).then(() => true).catch(() => false); i++) {
uniqueFilepath = (ext ? filepath.slice(0, -ext.length) : filepath) + `-${new Date().getTime()}${ext}`;
}
return uniqueFilepath;
}
function createFileUploadHandler({
directory = tmpdir(),
avoidFileConflicts = true,
file = defaultFilePathResolver,
filter,
maxPartSize = 3000000
} = {}) {
return async ({
name,
filename,
contentType,
data
}) => {
if (!filename || filter && !(await filter({
name,
filename,
contentType
}))) {
return undefined;
}
let dir = typeof directory === "string" ? directory : directory({
name,
filename,
contentType
});
if (!dir) {
return undefined;
}
let filedir = resolve(dir);
let path = typeof file === "string" ? file : file({
name,
filename,
contentType
});
if (!path) {
return undefined;
}
let filepath = resolve(filedir, path);
if (avoidFileConflicts) {
filepath = await uniqueFile(filepath);
}
await mkdir(dirname(filepath), {
recursive: true
}).catch(() => {});
let writeFileStream = createWriteStream(filepath);
let size = 0;
let deleteFile = false;
try {
for await (let chunk of data) {
size += chunk.byteLength;
if (size > maxPartSize) {
deleteFile = true;
throw new MaxPartSizeExceededError(name, maxPartSize);
}
writeFileStream.write(chunk);
}
} finally {
writeFileStream.end();
await promisify(finished)(writeFileStream);
if (deleteFile) {
await rm(filepath).catch(() => {});
}
}
// TODO: remove this typecast once TS fixed File class regression
// https://github.com/microsoft/TypeScript/issues/52166
return new NodeOnDiskFile(filepath, contentType);
};
}
// TODO: remove this `Omit` usage once TS fixed File class regression
// https://github.com/microsoft/TypeScript/issues/52166
class NodeOnDiskFile {
lastModified = 0;
webkitRelativePath = "";
// TODO: remove this property once TS fixed File class regression
// https://github.com/microsoft/TypeScript/issues/52166
prototype = File.prototype;
constructor(filepath, type, slicer) {
this.filepath = filepath;
this.type = type;
this.slicer = slicer;
this.name = basename(filepath);
}
get size() {
let stats = statSync(this.filepath);
if (this.slicer) {
let slice = this.slicer.end - this.slicer.start;
return slice < 0 ? 0 : slice > stats.size ? stats.size : slice;
}
return stats.size;
}
slice(start, end, type) {
var _this$slicer;
if (typeof start === "number" && start < 0) start = this.size + start;
if (typeof end === "number" && end < 0) end = this.size + end;
let startOffset = ((_this$slicer = this.slicer) === null || _this$slicer === void 0 ? void 0 : _this$slicer.start) || 0;
start = startOffset + (start || 0);
end = startOffset + (end || this.size);
return new NodeOnDiskFile(this.filepath, typeof type === "string" ? type : this.type, {
start,
end
}
// TODO: remove this typecast once TS fixed File class regression
// https://github.com/microsoft/TypeScript/issues/52166
);
}
async arrayBuffer() {
let stream = createReadStream(this.filepath);
if (this.slicer) {
stream = stream.pipe(streamSlice.slice(this.slicer.start, this.slicer.end));
}
return new Promise((resolve, reject) => {
let buf = [];
stream.on("data", chunk => buf.push(chunk));
stream.on("end", () => resolve(Buffer.concat(buf)));
stream.on("error", err => reject(err));
});
}
stream() {
let stream = createReadStream(this.filepath);
if (this.slicer) {
stream = stream.pipe(streamSlice.slice(this.slicer.start, this.slicer.end));
}
return createReadableStreamFromReadable(stream);
}
async text() {
return readableStreamToString(this.stream());
}
get [Symbol.toStringTag]() {
return "File";
}
remove() {
return unlink(this.filepath);
}
getFilePath() {
return this.filepath;
}
}
export { NodeOnDiskFile, createFileUploadHandler };