@nwire/storage-fs 0.7.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/LICENSE +21 -0
- package/README.md +65 -0
- package/dist/__tests__/storage-fs.test.d.ts +7 -0
- package/dist/__tests__/storage-fs.test.d.ts.map +1 -0
- package/dist/__tests__/storage-fs.test.js +79 -0
- package/dist/__tests__/storage-fs.test.js.map +1 -0
- package/dist/storage-fs.d.ts +42 -0
- package/dist/storage-fs.d.ts.map +1 -0
- package/dist/storage-fs.js +203 -0
- package/dist/storage-fs.js.map +1 -0
- package/package.json +41 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Alex Gefter / 200apps Ltd.
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
# @nwire/storage-fs
|
|
2
|
+
|
|
3
|
+
> Local filesystem adapter for `@nwire/storage` — dev / single-node only.
|
|
4
|
+
|
|
5
|
+
## What it does
|
|
6
|
+
|
|
7
|
+
Implements the `Storage` contract by writing files under a base directory. Keys map to relative paths under `baseDir`; paths that would escape `baseDir` are rejected (no `..` traversal). `url()` returns a `file://` URL — not presigned. For client-side fetches, proxy `/files/:key` through an HTTP wire to `fsStorage.getStream(key)`.
|
|
8
|
+
|
|
9
|
+
## Install
|
|
10
|
+
|
|
11
|
+
```bash
|
|
12
|
+
pnpm add @nwire/storage-fs @nwire/storage
|
|
13
|
+
```
|
|
14
|
+
|
|
15
|
+
## Quick start
|
|
16
|
+
|
|
17
|
+
```ts
|
|
18
|
+
import { fsStorage } from "@nwire/storage-fs";
|
|
19
|
+
import { storagePlugin } from "@nwire/storage";
|
|
20
|
+
import { defineApp } from "@nwire/forge";
|
|
21
|
+
|
|
22
|
+
defineApp("my-app", {
|
|
23
|
+
plugins: [
|
|
24
|
+
storagePlugin({
|
|
25
|
+
storage: fsStorage({ baseDir: "./var/uploads" }),
|
|
26
|
+
}),
|
|
27
|
+
],
|
|
28
|
+
});
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
## API surface
|
|
32
|
+
|
|
33
|
+
- `fsStorage({ baseDir })` — implements `Storage`.
|
|
34
|
+
|
|
35
|
+
## When to use
|
|
36
|
+
|
|
37
|
+
Local development without infra, single-node deployments where local disk is fine, tests that want real I/O. Don't use on multi-node deployments — no shared storage.
|
|
38
|
+
|
|
39
|
+
## Standalone use
|
|
40
|
+
|
|
41
|
+
For developers using `@nwire/storage-fs` **without the rest of Nwire** — pair it with any TypeScript project, any container, any HTTP framework.
|
|
42
|
+
|
|
43
|
+
```ts
|
|
44
|
+
// See the package's main entry (src/) for the standalone surface.
|
|
45
|
+
// The exports below work without @nwire/app or @nwire/forge.
|
|
46
|
+
import {} from /* ...standalone exports... */ "@nwire/storage-fs";
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
## Within nwire-app
|
|
50
|
+
|
|
51
|
+
For developers using this package as part of the Nwire stack — register it via `app.use(...)` or it auto-wires when you compose `createApp({ modules })`.
|
|
52
|
+
|
|
53
|
+
```ts
|
|
54
|
+
import { createApp } from "@nwire/forge";
|
|
55
|
+
|
|
56
|
+
const app = createApp({
|
|
57
|
+
/* ...config... */
|
|
58
|
+
});
|
|
59
|
+
// Adapter/plugin wiring happens here when applicable.
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
## See also
|
|
63
|
+
|
|
64
|
+
- [Architecture sketch §05 — Adapters tier](../../architecture-sketch.html#packages)
|
|
65
|
+
- Sibling packages: [@nwire/storage](../nwire-storage), [@nwire/storage-s3](../nwire-storage-s3)
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"storage-fs.test.d.ts","sourceRoot":"","sources":["../../src/__tests__/storage-fs.test.ts"],"names":[],"mappings":"AAAA;;;;GAIG"}
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `@nwire/storage-fs` — verifies the fs adapter against a temp dir.
|
|
3
|
+
* Same shape as the InMemoryStorage contract tests so we're checking
|
|
4
|
+
* adapter parity, not just file I/O.
|
|
5
|
+
*/
|
|
6
|
+
import { afterEach, beforeEach, describe, expect, it } from "vitest";
|
|
7
|
+
import { mkdtempSync, rmSync } from "node:fs";
|
|
8
|
+
import { tmpdir } from "node:os";
|
|
9
|
+
import { join } from "node:path";
|
|
10
|
+
import { fsStorage } from "../storage-fs";
|
|
11
|
+
import { StorageObjectNotFoundError, StorageUnsupportedError } from "@nwire/storage";
|
|
12
|
+
describe("fsStorage", () => {
|
|
13
|
+
let dir;
|
|
14
|
+
beforeEach(() => {
|
|
15
|
+
dir = mkdtempSync(join(tmpdir(), "nwire-storage-fs-"));
|
|
16
|
+
});
|
|
17
|
+
afterEach(() => {
|
|
18
|
+
rmSync(dir, { recursive: true, force: true });
|
|
19
|
+
});
|
|
20
|
+
it("put + get round-trips body + metadata via sidecar", async () => {
|
|
21
|
+
const s = fsStorage({ baseDir: dir });
|
|
22
|
+
await s.put("a/b.txt", "hello", {
|
|
23
|
+
contentType: "text/plain",
|
|
24
|
+
metadata: { uploader: "alice" },
|
|
25
|
+
});
|
|
26
|
+
const obj = await s.get("a/b.txt");
|
|
27
|
+
expect(obj.body.toString("utf-8")).toBe("hello");
|
|
28
|
+
expect(obj.contentType).toBe("text/plain");
|
|
29
|
+
expect(obj.metadata?.uploader).toBe("alice");
|
|
30
|
+
});
|
|
31
|
+
it("get throws StorageObjectNotFoundError when the file doesn't exist", async () => {
|
|
32
|
+
const s = fsStorage({ baseDir: dir });
|
|
33
|
+
await expect(s.get("nope.txt")).rejects.toBeInstanceOf(StorageObjectNotFoundError);
|
|
34
|
+
});
|
|
35
|
+
it("exists + delete round-trip", async () => {
|
|
36
|
+
const s = fsStorage({ baseDir: dir });
|
|
37
|
+
expect(await s.exists("x")).toBe(false);
|
|
38
|
+
await s.put("x", "y");
|
|
39
|
+
expect(await s.exists("x")).toBe(true);
|
|
40
|
+
await s.delete("x");
|
|
41
|
+
expect(await s.exists("x")).toBe(false);
|
|
42
|
+
});
|
|
43
|
+
it("list returns relative keys sorted", async () => {
|
|
44
|
+
const s = fsStorage({ baseDir: dir });
|
|
45
|
+
await s.put("posts/a", "1");
|
|
46
|
+
await s.put("posts/b", "2");
|
|
47
|
+
await s.put("users/x", "3");
|
|
48
|
+
const page = await s.list("posts/");
|
|
49
|
+
expect(page.entries.map((e) => e.key).sort()).toEqual(["posts/a", "posts/b"]);
|
|
50
|
+
});
|
|
51
|
+
it("getStream emits the body bytes", async () => {
|
|
52
|
+
const s = fsStorage({ baseDir: dir });
|
|
53
|
+
await s.put("stream.txt", "abc");
|
|
54
|
+
const stream = await s.getStream("stream.txt");
|
|
55
|
+
const chunks = [];
|
|
56
|
+
for await (const c of stream)
|
|
57
|
+
chunks.push(c);
|
|
58
|
+
expect(Buffer.concat(chunks).toString("utf-8")).toBe("abc");
|
|
59
|
+
});
|
|
60
|
+
it("rejects key traversal attempts", async () => {
|
|
61
|
+
const s = fsStorage({ baseDir: dir });
|
|
62
|
+
await expect(s.put("../escape.txt", "x")).rejects.toThrow(/escapes baseDir/);
|
|
63
|
+
});
|
|
64
|
+
it("url returns file:// for method=get", async () => {
|
|
65
|
+
const s = fsStorage({ baseDir: dir });
|
|
66
|
+
await s.put("a", "x");
|
|
67
|
+
const url = await s.url("a");
|
|
68
|
+
expect(url.startsWith("file://")).toBe(true);
|
|
69
|
+
});
|
|
70
|
+
it("url throws StorageUnsupportedError for method=put", async () => {
|
|
71
|
+
const s = fsStorage({ baseDir: dir });
|
|
72
|
+
await expect(s.url("a", { method: "put" })).rejects.toBeInstanceOf(StorageUnsupportedError);
|
|
73
|
+
});
|
|
74
|
+
it("healthCheck creates the base dir if missing", async () => {
|
|
75
|
+
const s = fsStorage({ baseDir: join(dir, "nested", "fresh") });
|
|
76
|
+
await expect(s.healthCheck()).resolves.toBeUndefined();
|
|
77
|
+
});
|
|
78
|
+
});
|
|
79
|
+
//# sourceMappingURL=storage-fs.test.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"storage-fs.test.js","sourceRoot":"","sources":["../../src/__tests__/storage-fs.test.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `@nwire/storage-fs` — local filesystem adapter.
|
|
3
|
+
*
|
|
4
|
+
* Implements the Storage contract by writing files under a base
|
|
5
|
+
* directory. Keys are mapped to relative paths under `baseDir`; paths
|
|
6
|
+
* that would escape `baseDir` are rejected (no `..` traversal).
|
|
7
|
+
*
|
|
8
|
+
* Use it for:
|
|
9
|
+
* - development without infra setup
|
|
10
|
+
* - single-node deployments where local disk is fine
|
|
11
|
+
* - tests that want real I/O instead of in-memory
|
|
12
|
+
*
|
|
13
|
+
* Don't use it for:
|
|
14
|
+
* - anything that runs on more than one node (no sharing)
|
|
15
|
+
* - large-scale uploads (no concurrency story beyond ext4)
|
|
16
|
+
*
|
|
17
|
+
* import { fsStorage } from "@nwire/storage-fs";
|
|
18
|
+
* import { storagePlugin } from "@nwire/storage";
|
|
19
|
+
*
|
|
20
|
+
* defineApp("my-app", {
|
|
21
|
+
* plugins: [storagePlugin({
|
|
22
|
+
* storage: fsStorage({ baseDir: "./var/uploads" }),
|
|
23
|
+
* })],
|
|
24
|
+
* });
|
|
25
|
+
*
|
|
26
|
+
* `url()` returns a `file://` URL. It's NOT a presigned URL — the file
|
|
27
|
+
* isn't directly fetchable by browsers from a remote host. If you need
|
|
28
|
+
* client-side fetches, run an HTTP wire that proxies `/files/:key` to
|
|
29
|
+
* `fsStorage.getStream(key)`.
|
|
30
|
+
*/
|
|
31
|
+
import { type Storage } from "@nwire/storage";
|
|
32
|
+
export interface FsStorageOptions {
|
|
33
|
+
/** Base directory where keys are stored. Created if missing. */
|
|
34
|
+
readonly baseDir: string;
|
|
35
|
+
/**
|
|
36
|
+
* Persist content-type + metadata in a sidecar `.meta.json`?
|
|
37
|
+
* Default `true`. Set false to avoid extra files when metadata isn't used.
|
|
38
|
+
*/
|
|
39
|
+
readonly persistMetadata?: boolean;
|
|
40
|
+
}
|
|
41
|
+
export declare function fsStorage(options: FsStorageOptions): Storage;
|
|
42
|
+
//# sourceMappingURL=storage-fs.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"storage-fs.d.ts","sourceRoot":"","sources":["../src/storage-fs.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA6BG;AAKH,OAAO,EAGL,KAAK,OAAO,EASb,MAAM,gBAAgB,CAAC;AAExB,MAAM,WAAW,gBAAgB;IAC/B,gEAAgE;IAChE,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;IACzB;;;OAGG;IACH,QAAQ,CAAC,eAAe,CAAC,EAAE,OAAO,CAAC;CACpC;AAED,wBAAgB,SAAS,CAAC,OAAO,EAAE,gBAAgB,GAAG,OAAO,CAE5D"}
|
|
@@ -0,0 +1,203 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `@nwire/storage-fs` — local filesystem adapter.
|
|
3
|
+
*
|
|
4
|
+
* Implements the Storage contract by writing files under a base
|
|
5
|
+
* directory. Keys are mapped to relative paths under `baseDir`; paths
|
|
6
|
+
* that would escape `baseDir` are rejected (no `..` traversal).
|
|
7
|
+
*
|
|
8
|
+
* Use it for:
|
|
9
|
+
* - development without infra setup
|
|
10
|
+
* - single-node deployments where local disk is fine
|
|
11
|
+
* - tests that want real I/O instead of in-memory
|
|
12
|
+
*
|
|
13
|
+
* Don't use it for:
|
|
14
|
+
* - anything that runs on more than one node (no sharing)
|
|
15
|
+
* - large-scale uploads (no concurrency story beyond ext4)
|
|
16
|
+
*
|
|
17
|
+
* import { fsStorage } from "@nwire/storage-fs";
|
|
18
|
+
* import { storagePlugin } from "@nwire/storage";
|
|
19
|
+
*
|
|
20
|
+
* defineApp("my-app", {
|
|
21
|
+
* plugins: [storagePlugin({
|
|
22
|
+
* storage: fsStorage({ baseDir: "./var/uploads" }),
|
|
23
|
+
* })],
|
|
24
|
+
* });
|
|
25
|
+
*
|
|
26
|
+
* `url()` returns a `file://` URL. It's NOT a presigned URL — the file
|
|
27
|
+
* isn't directly fetchable by browsers from a remote host. If you need
|
|
28
|
+
* client-side fetches, run an HTTP wire that proxies `/files/:key` to
|
|
29
|
+
* `fsStorage.getStream(key)`.
|
|
30
|
+
*/
|
|
31
|
+
import { promises as fs, createReadStream } from "node:fs";
|
|
32
|
+
import { dirname, join, resolve, sep } from "node:path";
|
|
33
|
+
import { pathToFileURL } from "node:url";
|
|
34
|
+
import { StorageObjectNotFoundError, StorageUnsupportedError, } from "@nwire/storage";
|
|
35
|
+
export function fsStorage(options) {
|
|
36
|
+
return new FsStorage(resolve(options.baseDir), options.persistMetadata ?? true);
|
|
37
|
+
}
|
|
38
|
+
class FsStorage {
|
|
39
|
+
baseDir;
|
|
40
|
+
persistMetadata;
|
|
41
|
+
constructor(baseDir, persistMetadata) {
|
|
42
|
+
this.baseDir = baseDir;
|
|
43
|
+
this.persistMetadata = persistMetadata;
|
|
44
|
+
}
|
|
45
|
+
/**
|
|
46
|
+
* Resolve a key to an absolute path UNDER baseDir. Rejects traversal
|
|
47
|
+
* via `..` segments or any other route that lands outside the base.
|
|
48
|
+
*/
|
|
49
|
+
path(key) {
|
|
50
|
+
const full = resolve(this.baseDir, key);
|
|
51
|
+
if (full !== this.baseDir && !full.startsWith(this.baseDir + sep)) {
|
|
52
|
+
throw new Error(`Refusing key that escapes baseDir: ${key}`);
|
|
53
|
+
}
|
|
54
|
+
return full;
|
|
55
|
+
}
|
|
56
|
+
metaPath(key) {
|
|
57
|
+
return this.path(key) + ".meta.json";
|
|
58
|
+
}
|
|
59
|
+
async put(key, body, opts) {
|
|
60
|
+
const target = this.path(key);
|
|
61
|
+
await fs.mkdir(dirname(target), { recursive: true });
|
|
62
|
+
const buf = toBuffer(body);
|
|
63
|
+
await fs.writeFile(target, buf);
|
|
64
|
+
if (this.persistMetadata && (opts?.contentType || opts?.cacheControl || opts?.metadata)) {
|
|
65
|
+
const sidecar = {
|
|
66
|
+
contentType: opts?.contentType,
|
|
67
|
+
cacheControl: opts?.cacheControl,
|
|
68
|
+
metadata: opts?.metadata ? { ...opts.metadata } : undefined,
|
|
69
|
+
};
|
|
70
|
+
await fs.writeFile(this.metaPath(key), JSON.stringify(sidecar));
|
|
71
|
+
}
|
|
72
|
+
return { key, size: buf.byteLength };
|
|
73
|
+
}
|
|
74
|
+
async get(key) {
|
|
75
|
+
const target = this.path(key);
|
|
76
|
+
let body;
|
|
77
|
+
try {
|
|
78
|
+
body = await fs.readFile(target);
|
|
79
|
+
}
|
|
80
|
+
catch (err) {
|
|
81
|
+
throw translateError(err, key);
|
|
82
|
+
}
|
|
83
|
+
const meta = await this.readMetadata(key);
|
|
84
|
+
const stats = await fs.stat(target);
|
|
85
|
+
return {
|
|
86
|
+
key,
|
|
87
|
+
body,
|
|
88
|
+
contentType: meta?.contentType,
|
|
89
|
+
size: stats.size,
|
|
90
|
+
metadata: meta?.metadata,
|
|
91
|
+
};
|
|
92
|
+
}
|
|
93
|
+
async getStream(key) {
|
|
94
|
+
const target = this.path(key);
|
|
95
|
+
try {
|
|
96
|
+
await fs.access(target);
|
|
97
|
+
}
|
|
98
|
+
catch (err) {
|
|
99
|
+
throw translateError(err, key);
|
|
100
|
+
}
|
|
101
|
+
return createReadStream(target);
|
|
102
|
+
}
|
|
103
|
+
async delete(key) {
|
|
104
|
+
const target = this.path(key);
|
|
105
|
+
await fs.rm(target, { force: true });
|
|
106
|
+
if (this.persistMetadata)
|
|
107
|
+
await fs.rm(this.metaPath(key), { force: true });
|
|
108
|
+
}
|
|
109
|
+
async exists(key) {
|
|
110
|
+
try {
|
|
111
|
+
await fs.access(this.path(key));
|
|
112
|
+
return true;
|
|
113
|
+
}
|
|
114
|
+
catch {
|
|
115
|
+
return false;
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
async list(prefix, opts) {
|
|
119
|
+
const root = prefix ? this.path(prefix) : this.baseDir;
|
|
120
|
+
const limit = opts?.limit ?? 1000;
|
|
121
|
+
const cursor = opts?.cursor;
|
|
122
|
+
const keys = [];
|
|
123
|
+
await walk(root, async (full) => {
|
|
124
|
+
if (full.endsWith(".meta.json"))
|
|
125
|
+
return;
|
|
126
|
+
const rel = full.slice(this.baseDir.length + 1).split(sep).join("/");
|
|
127
|
+
// The `prefix` argument is a prefix-match on keys; honor it when
|
|
128
|
+
// root is the base dir (caller passed an unanchored prefix).
|
|
129
|
+
if (prefix && !rel.startsWith(prefix))
|
|
130
|
+
return;
|
|
131
|
+
keys.push(rel);
|
|
132
|
+
});
|
|
133
|
+
keys.sort();
|
|
134
|
+
const startAt = cursor ? keys.findIndex((k) => k > cursor) : 0;
|
|
135
|
+
const slice = keys.slice(Math.max(0, startAt), Math.max(0, startAt) + limit);
|
|
136
|
+
const entries = await Promise.all(slice.map(async (k) => {
|
|
137
|
+
const stats = await fs.stat(this.path(k));
|
|
138
|
+
return { key: k, size: stats.size, modifiedAt: stats.mtime };
|
|
139
|
+
}));
|
|
140
|
+
const last = entries.at(-1)?.key;
|
|
141
|
+
const more = last && keys.indexOf(last) < keys.length - 1;
|
|
142
|
+
return { entries, nextCursor: more ? last : undefined };
|
|
143
|
+
}
|
|
144
|
+
/**
|
|
145
|
+
* fs doesn't speak presigned URLs. We return `file://` for completeness
|
|
146
|
+
* (useful for tests and local tooling), but for `method: "put"` we
|
|
147
|
+
* throw — there's no sane "client uploads directly here" story over fs.
|
|
148
|
+
*/
|
|
149
|
+
async url(key, opts) {
|
|
150
|
+
if ((opts?.method ?? "get") === "put") {
|
|
151
|
+
throw new StorageUnsupportedError("fsStorage doesn't support presigned PUT URLs — clients can't upload " +
|
|
152
|
+
"directly to a server file path. Mount a real HTTP upload endpoint instead.");
|
|
153
|
+
}
|
|
154
|
+
return pathToFileURL(this.path(key)).toString();
|
|
155
|
+
}
|
|
156
|
+
async healthCheck() {
|
|
157
|
+
await fs.mkdir(this.baseDir, { recursive: true });
|
|
158
|
+
await fs.access(this.baseDir);
|
|
159
|
+
}
|
|
160
|
+
async readMetadata(key) {
|
|
161
|
+
if (!this.persistMetadata)
|
|
162
|
+
return undefined;
|
|
163
|
+
try {
|
|
164
|
+
const raw = await fs.readFile(this.metaPath(key), "utf-8");
|
|
165
|
+
return JSON.parse(raw);
|
|
166
|
+
}
|
|
167
|
+
catch {
|
|
168
|
+
return undefined;
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
function toBuffer(body) {
|
|
173
|
+
if (Buffer.isBuffer(body))
|
|
174
|
+
return body;
|
|
175
|
+
if (typeof body === "string")
|
|
176
|
+
return Buffer.from(body, "utf-8");
|
|
177
|
+
return Buffer.from(body);
|
|
178
|
+
}
|
|
179
|
+
function translateError(err, key) {
|
|
180
|
+
if (err && typeof err === "object" && err.code === "ENOENT") {
|
|
181
|
+
return new StorageObjectNotFoundError(key);
|
|
182
|
+
}
|
|
183
|
+
return err;
|
|
184
|
+
}
|
|
185
|
+
async function walk(root, onFile) {
|
|
186
|
+
let entries;
|
|
187
|
+
try {
|
|
188
|
+
entries = await fs.readdir(root, { withFileTypes: true });
|
|
189
|
+
}
|
|
190
|
+
catch {
|
|
191
|
+
return; // missing root counts as empty
|
|
192
|
+
}
|
|
193
|
+
for (const ent of entries) {
|
|
194
|
+
const full = join(root, ent.name);
|
|
195
|
+
if (ent.isDirectory()) {
|
|
196
|
+
await walk(full, onFile);
|
|
197
|
+
}
|
|
198
|
+
else if (ent.isFile()) {
|
|
199
|
+
await onFile(full);
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
//# sourceMappingURL=storage-fs.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"storage-fs.js","sourceRoot":"","sources":["../src/storage-fs.ts"],"names":[],"mappings":"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"}
|
package/package.json
ADDED
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@nwire/storage-fs",
|
|
3
|
+
"version": "0.7.0",
|
|
4
|
+
"description": "Nwire — local filesystem storage adapter. Implements the Storage contract against a base directory. Suitable for development, single-node deployments, and tests.",
|
|
5
|
+
"keywords": [
|
|
6
|
+
"adapter",
|
|
7
|
+
"filesystem",
|
|
8
|
+
"fs",
|
|
9
|
+
"nwire",
|
|
10
|
+
"storage"
|
|
11
|
+
],
|
|
12
|
+
"files": [
|
|
13
|
+
"dist",
|
|
14
|
+
"README.md"
|
|
15
|
+
],
|
|
16
|
+
"type": "module",
|
|
17
|
+
"main": "./dist/storage-fs.js",
|
|
18
|
+
"types": "./dist/storage-fs.d.ts",
|
|
19
|
+
"exports": {
|
|
20
|
+
".": {
|
|
21
|
+
"import": "./dist/storage-fs.js",
|
|
22
|
+
"types": "./dist/storage-fs.d.ts"
|
|
23
|
+
}
|
|
24
|
+
},
|
|
25
|
+
"publishConfig": {
|
|
26
|
+
"access": "public"
|
|
27
|
+
},
|
|
28
|
+
"dependencies": {
|
|
29
|
+
"@nwire/storage": "0.7.0"
|
|
30
|
+
},
|
|
31
|
+
"devDependencies": {
|
|
32
|
+
"@types/node": "^22.19.9",
|
|
33
|
+
"typescript": "^5.9.3",
|
|
34
|
+
"vitest": "^4.1.6"
|
|
35
|
+
},
|
|
36
|
+
"scripts": {
|
|
37
|
+
"build": "tsc",
|
|
38
|
+
"dev": "tsc --watch",
|
|
39
|
+
"typecheck": "tsc --noEmit"
|
|
40
|
+
}
|
|
41
|
+
}
|