Skip to content
This repository was archived by the owner on Jul 25, 2025. It is now read-only.

Commit f1063ac

Browse files
committed
Fix regression in file storage
Fixes #53
1 parent b6c9d67 commit f1063ac

File tree

7 files changed

+89
-10
lines changed

7 files changed

+89
-10
lines changed

packages/file-storage/CHANGELOG.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,10 @@
22

33
This is the changelog for [`file-storage`](https://github.com/mjackson/remix-the-web/tree/main/packages/file-storage). It follows [semantic versioning](https://semver.org/).
44

5+
## HEAD
6+
7+
- Fix regression when using `LocalFileStorage` together with `form-data-parser` (see #53)
8+
59
## v0.6.0 (2025-02-04)
610

711
- BREAKING CHANGE: `LocalFileStorage` now uses 2 characters for shard directory names instead of 8.

packages/file-storage/package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -79,6 +79,7 @@
7979
"@mjackson/lazy-file": "workspace:^"
8080
},
8181
"devDependencies": {
82+
"@mjackson/form-data-parser": "workspace:^",
8283
"@types/node": "^20.14.10",
8384
"tsup": "^8.3.5"
8485
},

packages/file-storage/src/lib/local-file-storage.test.ts

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ import * as assert from 'node:assert/strict';
22
import { afterEach, describe, it } from 'node:test';
33
import * as fs from 'node:fs';
44
import * as path from 'node:path';
5+
import { parseFormData } from '@mjackson/form-data-parser';
56

67
import { LocalFileStorage } from './local-file-storage.ts';
78

@@ -136,4 +137,41 @@ describe('LocalFileStorage', () => {
136137
assert.ok(retrieved2);
137138
assert.equal(await retrieved2.text(), 'Hello, universe!');
138139
});
140+
141+
describe('integration with form-data-parser', () => {
142+
it('stores and lists file uploads', async () => {
143+
let storage = new LocalFileStorage(directory);
144+
145+
let boundary = '----WebKitFormBoundary7MA4YWxkTrZu0gW';
146+
let request = new Request('http://example.com', {
147+
method: 'POST',
148+
headers: {
149+
'Content-Type': `multipart/form-data; boundary=${boundary}`,
150+
},
151+
body: [
152+
`--${boundary}`,
153+
'Content-Disposition: form-data; name="hello"; filename="hello.txt"',
154+
'Content-Type: text/plain',
155+
'',
156+
'Hello, world!',
157+
`--${boundary}--`,
158+
].join('\r\n'),
159+
});
160+
161+
await parseFormData(request, async (file) => {
162+
await storage.set('hello', file);
163+
});
164+
165+
assert.ok(await storage.has('hello'));
166+
167+
let { files } = await storage.list({ includeMetadata: true });
168+
169+
assert.equal(files.length, 1);
170+
assert.equal(files[0].key, 'hello');
171+
assert.equal(files[0].name, 'hello.txt');
172+
assert.equal(files[0].size, 13);
173+
assert.equal(files[0].type, 'text/plain');
174+
assert.ok(files[0].lastModified);
175+
});
176+
});
139177
});

packages/file-storage/src/lib/local-file-storage.ts

Lines changed: 4 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -3,12 +3,7 @@ import * as fsp from 'node:fs/promises';
33
import * as path from 'node:path';
44
import { openFile, writeFile } from '@mjackson/lazy-file/fs';
55

6-
import {
7-
type FileStorage,
8-
type FileMetadata,
9-
type ListOptions,
10-
type ListResult,
11-
} from './file-storage.ts';
6+
import type { FileStorage, FileMetadata, ListOptions, ListResult } from './file-storage.ts';
127

138
/**
149
* A `FileStorage` that is backed by a directory on the local filesystem.
@@ -105,7 +100,8 @@ export class LocalFileStorage implements FileStorage {
105100
}
106101

107102
if (includeMetadata) {
108-
files.push(meta);
103+
let size = (await fsp.stat(path.join(this.#dirname, subdir.name, `${hash}.dat`))).size;
104+
files.push({ ...meta, size });
109105
} else {
110106
files.push({ key: meta.key });
111107
}
@@ -151,11 +147,10 @@ export class LocalFileStorage implements FileStorage {
151147

152148
await writeFile(filePath, file);
153149

154-
let meta: FileMetadata = {
150+
let meta: Omit<FileMetadata, 'size'> = {
155151
key,
156152
lastModified: file.lastModified,
157153
name: file.name,
158-
size: file.size,
159154
type: file.type,
160155
};
161156
await fsp.writeFile(metaPath, JSON.stringify(meta));

packages/file-storage/src/lib/memory-file-storage.test.ts

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import * as assert from 'node:assert/strict';
22
import { describe, it } from 'node:test';
3+
import { parseFormData } from '@mjackson/form-data-parser';
34

45
import { MemoryFileStorage } from './memory-file-storage.ts';
56

@@ -97,4 +98,41 @@ describe('MemoryFileStorage', () => {
9798
files.forEach((f) => assert.ok('size' in f));
9899
files.forEach((f) => assert.ok('type' in f));
99100
});
101+
102+
describe('integration with form-data-parser', () => {
103+
it('stores and lists file uploads', async () => {
104+
let storage = new MemoryFileStorage();
105+
106+
let boundary = '----WebKitFormBoundaryzv5f5B8XUeVl7e0A';
107+
let request = new Request('http://example.com', {
108+
method: 'POST',
109+
headers: {
110+
'Content-Type': `multipart/form-data; boundary=${boundary}`,
111+
},
112+
body: [
113+
`--${boundary}`,
114+
'Content-Disposition: form-data; name="a"; filename="hello.txt"',
115+
'Content-Type: text/plain',
116+
'',
117+
'Hello, world!',
118+
`--${boundary}--`,
119+
].join('\r\n'),
120+
});
121+
122+
await parseFormData(request, async (file) => {
123+
await storage.put('hello', file);
124+
});
125+
126+
assert.ok(storage.has('hello'));
127+
128+
let { files } = storage.list({ includeMetadata: true });
129+
130+
assert.equal(files.length, 1);
131+
assert.equal(files[0].key, 'hello');
132+
assert.equal(files[0].name, 'hello.txt');
133+
assert.equal(files[0].size, 13);
134+
assert.equal(files[0].type, 'text/plain');
135+
assert.ok(files[0].lastModified);
136+
});
137+
});
100138
});

packages/file-storage/src/lib/memory-file-storage.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { type FileStorage, type ListOptions, type ListResult } from './file-storage.ts';
1+
import type { FileStorage, ListOptions, ListResult } from './file-storage.ts';
22

33
/**
44
* A simple, in-memory implementation of the `FileStorage` interface.

pnpm-lock.yaml

Lines changed: 3 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)