@nwire/storage-s3 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 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,68 @@
1
+ # @nwire/storage-s3
2
+
3
+ > S3-compatible adapter for `@nwire/storage` — AWS S3, MinIO, R2, B2, Spaces, Wasabi.
4
+
5
+ ## What it does
6
+
7
+ A thin shim over `@aws-sdk/client-s3` that satisfies the `@nwire/storage` `Storage` contract. Translates calls to S3 commands, maps NotFound to `StorageObjectNotFoundError`, uses `@aws-sdk/s3-request-presigner` for `url()`, and ships a `healthCheck` that calls `HeadBucket` (fast, cheap, exact).
8
+
9
+ ## Install
10
+
11
+ ```bash
12
+ pnpm add @nwire/storage-s3 @nwire/storage @aws-sdk/client-s3 @aws-sdk/s3-request-presigner
13
+ ```
14
+
15
+ ## Quick start
16
+
17
+ ```ts
18
+ import { s3Storage } from "@nwire/storage-s3";
19
+ import { storagePlugin } from "@nwire/storage";
20
+ import { defineApp } from "@nwire/forge";
21
+
22
+ const storage = s3Storage({
23
+ bucket: "uploads",
24
+ region: "us-east-1",
25
+ // For MinIO:
26
+ // endpoint: "http://localhost:9000",
27
+ // forcePathStyle: true,
28
+ // credentials: { accessKeyId: "minioadmin", secretAccessKey: "minioadmin" },
29
+ });
30
+
31
+ defineApp("my-app", { plugins: [storagePlugin({ storage })] });
32
+ ```
33
+
34
+ ## API surface
35
+
36
+ - `s3Storage({ bucket, region, endpoint?, forcePathStyle?, credentials? })` — implements `Storage`.
37
+
38
+ ## When to use
39
+
40
+ Anything multi-node or production. `nwire infra up` boots MinIO locally at `:9000` so the same adapter covers dev + prod. Fits L3 and up.
41
+
42
+ ## Standalone use
43
+
44
+ For developers using `@nwire/storage-s3` **without the rest of Nwire** — pair it with any TypeScript project, any container, any HTTP framework.
45
+
46
+ ```ts
47
+ // See the package's main entry (src/) for the standalone surface.
48
+ // The exports below work without @nwire/app or @nwire/forge.
49
+ import {} from /* ...standalone exports... */ "@nwire/storage-s3";
50
+ ```
51
+
52
+ ## Within nwire-app
53
+
54
+ 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 })`.
55
+
56
+ ```ts
57
+ import { createApp } from "@nwire/forge";
58
+
59
+ const app = createApp({
60
+ /* ...config... */
61
+ });
62
+ // Adapter/plugin wiring happens here when applicable.
63
+ ```
64
+
65
+ ## See also
66
+
67
+ - [Architecture sketch §05 — Adapters tier](../../architecture-sketch.html#packages)
68
+ - Sibling packages: [@nwire/storage](../nwire-storage), [@nwire/storage-fs](../nwire-storage-fs)
@@ -0,0 +1,23 @@
1
+ /**
2
+ * Integration test — exercises `@nwire/storage-s3` against real MinIO.
3
+ *
4
+ * MinIO + the `nwire-dev` bucket come from the workspace docker-compose
5
+ * (`nwire infra up` or `docker compose up -d minio minio-bootstrap`).
6
+ * When MinIO isn't reachable on `127.0.0.1:9000`, the whole suite skips
7
+ * cleanly — the default `pnpm vitest run` stays fast and self-contained,
8
+ * and CI gets a deterministic skip rather than a flaky failure.
9
+ *
10
+ * Why exercise MinIO instead of trusting the unit tests:
11
+ *
12
+ * - Presigned URLs (`url()`) can only be verified against a server
13
+ * that signs the URL the same way it later validates it. A stub
14
+ * can't catch sig-version mismatches, path-style bugs, or auth
15
+ * header drift across SDK upgrades.
16
+ * - `forcePathStyle` + `endpoint` interactions are MinIO-specific and
17
+ * break silently if the SDK changes how it composes URLs.
18
+ * - Real round-trips through the S3 protocol catch encoding bugs
19
+ * (binary bodies, metadata header casing, etag quoting) that pure
20
+ * command-construction stubs miss.
21
+ */
22
+ export {};
23
+ //# sourceMappingURL=storage-s3.integration.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"storage-s3.integration.test.d.ts","sourceRoot":"","sources":["../../src/__tests__/storage-s3.integration.test.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;GAoBG"}
@@ -0,0 +1,150 @@
1
+ /**
2
+ * Integration test — exercises `@nwire/storage-s3` against real MinIO.
3
+ *
4
+ * MinIO + the `nwire-dev` bucket come from the workspace docker-compose
5
+ * (`nwire infra up` or `docker compose up -d minio minio-bootstrap`).
6
+ * When MinIO isn't reachable on `127.0.0.1:9000`, the whole suite skips
7
+ * cleanly — the default `pnpm vitest run` stays fast and self-contained,
8
+ * and CI gets a deterministic skip rather than a flaky failure.
9
+ *
10
+ * Why exercise MinIO instead of trusting the unit tests:
11
+ *
12
+ * - Presigned URLs (`url()`) can only be verified against a server
13
+ * that signs the URL the same way it later validates it. A stub
14
+ * can't catch sig-version mismatches, path-style bugs, or auth
15
+ * header drift across SDK upgrades.
16
+ * - `forcePathStyle` + `endpoint` interactions are MinIO-specific and
17
+ * break silently if the SDK changes how it composes URLs.
18
+ * - Real round-trips through the S3 protocol catch encoding bugs
19
+ * (binary bodies, metadata header casing, etag quoting) that pure
20
+ * command-construction stubs miss.
21
+ */
22
+ import { describe, it, expect, beforeAll, afterAll } from "vitest";
23
+ import { randomUUID } from "node:crypto";
24
+ import { isReachable } from "@nwire/test-kit";
25
+ import { s3Storage } from "../storage-s3";
26
+ import { StorageObjectNotFoundError } from "@nwire/storage";
27
+ const MINIO_HOST = "127.0.0.1";
28
+ const MINIO_PORT = 9000;
29
+ const BUCKET = "nwire-dev";
30
+ const reachable = await isReachable(MINIO_HOST, MINIO_PORT, 800);
31
+ describe.skipIf(!reachable)("storage-s3 ↔ real MinIO", () => {
32
+ let storage;
33
+ // Namespace every run so concurrent CI workers and rerun-after-fail
34
+ // don't trip over each other's keys.
35
+ const ns = `tests/${randomUUID().slice(0, 8)}/`;
36
+ beforeAll(() => {
37
+ storage = s3Storage({
38
+ bucket: BUCKET,
39
+ region: "us-east-1",
40
+ endpoint: `http://${MINIO_HOST}:${MINIO_PORT}`,
41
+ forcePathStyle: true,
42
+ credentials: { accessKeyId: "minioadmin", secretAccessKey: "minioadmin" },
43
+ keyPrefix: ns,
44
+ });
45
+ });
46
+ afterAll(async () => {
47
+ // Best-effort cleanup — list and delete anything our prefix wrote.
48
+ try {
49
+ const page = await storage.list();
50
+ for (const e of page.entries)
51
+ await storage.delete(e.key);
52
+ }
53
+ catch { /* ignore — test isolation should keep these uncontended */ }
54
+ await storage.shutdown?.();
55
+ });
56
+ it("HeadBucket succeeds — healthCheck passes against MinIO", async () => {
57
+ await expect(storage.healthCheck()).resolves.toBeUndefined();
58
+ });
59
+ it("put → get round-trips a UTF-8 string body with the right content type", async () => {
60
+ const result = await storage.put("hello.txt", "Hello, MinIO!", {
61
+ contentType: "text/plain",
62
+ metadata: { sender: "phase-77" },
63
+ });
64
+ expect(result.size).toBe(13);
65
+ expect(result.etag).toMatch(/^"?[a-f0-9]{32}/i);
66
+ const back = await storage.get("hello.txt");
67
+ expect(back.body.toString("utf-8")).toBe("Hello, MinIO!");
68
+ expect(back.contentType).toBe("text/plain");
69
+ expect(back.size).toBe(13);
70
+ // MinIO + S3 normalize metadata to lowercase keys.
71
+ expect(back.metadata?.sender).toBe("phase-77");
72
+ });
73
+ it("put accepts a binary Buffer and preserves bytes exactly", async () => {
74
+ const bytes = Buffer.from([0xde, 0xad, 0xbe, 0xef, 0x00, 0xff, 0x7f]);
75
+ await storage.put("binary.bin", bytes);
76
+ const back = await storage.get("binary.bin");
77
+ expect(Buffer.compare(back.body, bytes)).toBe(0);
78
+ });
79
+ it("get on a missing key throws StorageObjectNotFoundError", async () => {
80
+ await expect(storage.get("missing/" + randomUUID())).rejects.toBeInstanceOf(StorageObjectNotFoundError);
81
+ });
82
+ it("exists() flips false → true → false across put + delete", async () => {
83
+ const key = "lifecycle.txt";
84
+ expect(await storage.exists(key)).toBe(false);
85
+ await storage.put(key, "x");
86
+ expect(await storage.exists(key)).toBe(true);
87
+ await storage.delete(key);
88
+ expect(await storage.exists(key)).toBe(false);
89
+ });
90
+ it("list() returns the entries we wrote, with our prefix stripped", async () => {
91
+ // Fresh sub-namespace to keep the assertion simple.
92
+ const inner = s3Storage({
93
+ bucket: BUCKET,
94
+ region: "us-east-1",
95
+ endpoint: `http://${MINIO_HOST}:${MINIO_PORT}`,
96
+ forcePathStyle: true,
97
+ credentials: { accessKeyId: "minioadmin", secretAccessKey: "minioadmin" },
98
+ keyPrefix: `${ns}list-test/`,
99
+ });
100
+ try {
101
+ await inner.put("a.txt", "1");
102
+ await inner.put("b.txt", "22");
103
+ await inner.put("c/d.txt", "333");
104
+ const page = await inner.list();
105
+ const keys = page.entries.map((e) => e.key).sort();
106
+ expect(keys).toEqual(["a.txt", "b.txt", "c/d.txt"]);
107
+ const a = page.entries.find((e) => e.key === "a.txt");
108
+ expect(a.size).toBe(1);
109
+ }
110
+ finally {
111
+ await inner.shutdown?.();
112
+ }
113
+ });
114
+ it("presigned GET url roundtrips via plain fetch", async () => {
115
+ const key = "presigned.txt";
116
+ await storage.put(key, "via signed url", { contentType: "text/plain" });
117
+ const url = await storage.url(key, { expiresInSeconds: 60 });
118
+ expect(url).toMatch(/X-Amz-Signature=/);
119
+ const res = await fetch(url);
120
+ expect(res.status).toBe(200);
121
+ expect(await res.text()).toBe("via signed url");
122
+ });
123
+ it("presigned PUT url accepts a fetch-driven upload", async () => {
124
+ const key = "uploaded-via-signed.txt";
125
+ const url = await storage.url(key, {
126
+ method: "put",
127
+ contentType: "text/plain",
128
+ expiresInSeconds: 60,
129
+ });
130
+ const res = await fetch(url, {
131
+ method: "PUT",
132
+ headers: { "content-type": "text/plain" },
133
+ body: "uploaded through a presigned URL",
134
+ });
135
+ expect(res.ok).toBe(true);
136
+ const back = await storage.get(key);
137
+ expect(back.body.toString("utf-8")).toBe("uploaded through a presigned URL");
138
+ });
139
+ it("getStream() yields the same bytes as get()", async () => {
140
+ const key = "stream.txt";
141
+ await storage.put(key, "stream me");
142
+ const stream = await storage.getStream(key);
143
+ const chunks = [];
144
+ for await (const c of stream) {
145
+ chunks.push(Buffer.isBuffer(c) ? c : Buffer.from(c));
146
+ }
147
+ expect(Buffer.concat(chunks).toString("utf-8")).toBe("stream me");
148
+ });
149
+ });
150
+ //# sourceMappingURL=storage-s3.integration.test.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"storage-s3.integration.test.js","sourceRoot":"","sources":["../../src/__tests__/storage-s3.integration.test.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,13 @@
1
+ /**
2
+ * `@nwire/storage-s3` — unit tests with a hand-rolled `S3Client.send`
3
+ * stub. We're verifying:
4
+ *
5
+ * - the right command + parameters reach the SDK
6
+ * - GetObject NotFound is translated to StorageObjectNotFoundError
7
+ * - body assembly works for an async-iterable response (the Node case)
8
+ *
9
+ * Integration tests against real MinIO live elsewhere — the docker-
10
+ * compose has the bucket ready when those run.
11
+ */
12
+ export {};
13
+ //# sourceMappingURL=storage-s3.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"storage-s3.test.d.ts","sourceRoot":"","sources":["../../src/__tests__/storage-s3.test.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG"}
@@ -0,0 +1,125 @@
1
+ /**
2
+ * `@nwire/storage-s3` — unit tests with a hand-rolled `S3Client.send`
3
+ * stub. We're verifying:
4
+ *
5
+ * - the right command + parameters reach the SDK
6
+ * - GetObject NotFound is translated to StorageObjectNotFoundError
7
+ * - body assembly works for an async-iterable response (the Node case)
8
+ *
9
+ * Integration tests against real MinIO live elsewhere — the docker-
10
+ * compose has the bucket ready when those run.
11
+ */
12
+ import { describe, it, expect, vi } from "vitest";
13
+ import { Readable } from "node:stream";
14
+ import { s3Storage } from "../storage-s3";
15
+ import { StorageObjectNotFoundError } from "@nwire/storage";
16
+ function makeClientStub(responses) {
17
+ const calls = [];
18
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
19
+ const send = vi.fn(async (cmd) => {
20
+ const name = cmd.constructor.name;
21
+ calls.push({ name, input: cmd.input });
22
+ const reply = responses[name];
23
+ if (reply instanceof Error)
24
+ throw reply;
25
+ return reply ?? {};
26
+ });
27
+ const stub = { send, destroy: vi.fn() };
28
+ return { stub, send, calls };
29
+ }
30
+ describe("s3Storage — command translation", () => {
31
+ it("put sends a PutObjectCommand with the right Bucket + Key + Body", async () => {
32
+ const { stub, calls } = makeClientStub({
33
+ PutObjectCommand: { ETag: '"abc"' },
34
+ });
35
+ const storage = s3Storage({ bucket: "uploads", client: stub });
36
+ const result = await storage.put("a/b.txt", "hello", {
37
+ contentType: "text/plain",
38
+ metadata: { user: "alice" },
39
+ });
40
+ expect(result.etag).toBe('"abc"');
41
+ expect(result.size).toBe(5);
42
+ expect(calls).toEqual([{
43
+ name: "PutObjectCommand",
44
+ input: {
45
+ Bucket: "uploads",
46
+ Key: "a/b.txt",
47
+ Body: "hello",
48
+ ContentType: "text/plain",
49
+ CacheControl: undefined,
50
+ Metadata: { user: "alice" },
51
+ },
52
+ }]);
53
+ });
54
+ it("get assembles the body from a Node Readable stream", async () => {
55
+ const { stub } = makeClientStub({
56
+ GetObjectCommand: {
57
+ Body: Readable.from(["hel", "lo"]),
58
+ ContentType: "text/plain",
59
+ ContentLength: 5,
60
+ ETag: '"abc"',
61
+ },
62
+ });
63
+ const storage = s3Storage({ bucket: "uploads", client: stub });
64
+ const obj = await storage.get("a/b.txt");
65
+ expect(obj.body.toString("utf-8")).toBe("hello");
66
+ expect(obj.contentType).toBe("text/plain");
67
+ expect(obj.size).toBe(5);
68
+ });
69
+ it("get translates NoSuchKey into StorageObjectNotFoundError", async () => {
70
+ const err = Object.assign(new Error("not found"), { name: "NoSuchKey" });
71
+ const { stub } = makeClientStub({ GetObjectCommand: err });
72
+ const storage = s3Storage({ bucket: "uploads", client: stub });
73
+ await expect(storage.get("missing.txt")).rejects.toBeInstanceOf(StorageObjectNotFoundError);
74
+ });
75
+ it("exists returns false on 404, true on success", async () => {
76
+ const { stub: ok } = makeClientStub({
77
+ HeadObjectCommand: { ContentLength: 1 },
78
+ });
79
+ expect(await s3Storage({ bucket: "u", client: ok }).exists("a")).toBe(true);
80
+ const err = Object.assign(new Error(""), {
81
+ name: "NotFound",
82
+ $metadata: { httpStatusCode: 404 },
83
+ });
84
+ const { stub: missing } = makeClientStub({ HeadObjectCommand: err });
85
+ expect(await s3Storage({ bucket: "u", client: missing }).exists("a")).toBe(false);
86
+ });
87
+ it("list maps Contents into StorageEntry array", async () => {
88
+ const { stub } = makeClientStub({
89
+ ListObjectsV2Command: {
90
+ Contents: [
91
+ { Key: "a", Size: 1, LastModified: new Date(0), ETag: '"x"' },
92
+ { Key: "b", Size: 2, LastModified: new Date(1), ETag: '"y"' },
93
+ ],
94
+ IsTruncated: false,
95
+ },
96
+ });
97
+ const page = await s3Storage({ bucket: "u", client: stub }).list();
98
+ expect(page.entries.map((e) => e.key)).toEqual(["a", "b"]);
99
+ expect(page.nextCursor).toBeUndefined();
100
+ });
101
+ it("keyPrefix is applied to every operation and stripped on list", async () => {
102
+ const { stub, calls } = makeClientStub({
103
+ PutObjectCommand: {},
104
+ ListObjectsV2Command: {
105
+ Contents: [{ Key: "tenants/acme/a", Size: 1 }],
106
+ },
107
+ });
108
+ const storage = s3Storage({ bucket: "u", client: stub, keyPrefix: "tenants/acme/" });
109
+ await storage.put("a", "x");
110
+ const page = await storage.list();
111
+ expect(calls[0].input).toMatchObject({ Key: "tenants/acme/a" });
112
+ expect(page.entries[0].key).toBe("a");
113
+ });
114
+ it("healthCheck calls HeadBucket", async () => {
115
+ const { stub, calls } = makeClientStub({ HeadBucketCommand: {} });
116
+ await s3Storage({ bucket: "u", client: stub }).healthCheck();
117
+ expect(calls[0].name).toBe("HeadBucketCommand");
118
+ });
119
+ it("shutdown destroys the SDK client", async () => {
120
+ const { stub } = makeClientStub({});
121
+ await s3Storage({ bucket: "u", client: stub }).shutdown();
122
+ expect(stub.destroy).toHaveBeenCalled();
123
+ });
124
+ });
125
+ //# sourceMappingURL=storage-s3.test.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"storage-s3.test.js","sourceRoot":"","sources":["../../src/__tests__/storage-s3.test.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,68 @@
1
+ /**
2
+ * `@nwire/storage-s3` — S3-compatible adapter.
3
+ *
4
+ * Works with anything that speaks the S3 API:
5
+ * - AWS S3 (default)
6
+ * - MinIO (set `endpoint` + `forcePathStyle: true`)
7
+ * - Cloudflare R2
8
+ * - Backblaze B2
9
+ * - DigitalOcean Spaces, Wasabi, Linode Object Storage, etc.
10
+ *
11
+ * The adapter is a thin shim over `@aws-sdk/client-s3`:
12
+ * - Translates the Nwire Storage contract to S3 commands.
13
+ * - Maps NotFound errors to `StorageObjectNotFoundError`.
14
+ * - Uses `@aws-sdk/s3-request-presigner` for `url()` (presigned URLs).
15
+ * - Provides a `healthCheck` that calls `HeadBucket` — fast, cheap, exact.
16
+ *
17
+ * import { s3Storage } from "@nwire/storage-s3";
18
+ * import { storagePlugin } from "@nwire/storage";
19
+ *
20
+ * const storage = s3Storage({
21
+ * bucket: "uploads",
22
+ * region: "us-east-1",
23
+ * // MinIO:
24
+ * // endpoint: "http://localhost:9000",
25
+ * // forcePathStyle: true,
26
+ * // credentials: { accessKeyId: "minioadmin", secretAccessKey: "minioadmin" },
27
+ * });
28
+ *
29
+ * defineApp("my-app", { plugins: [storagePlugin({ storage })] });
30
+ */
31
+ import { S3Client } from "@aws-sdk/client-s3";
32
+ import { type Storage } from "@nwire/storage";
33
+ export interface S3StorageOptions {
34
+ /** Bucket name. Required. */
35
+ readonly bucket: string;
36
+ /** AWS region (or any value MinIO/R2 expects). */
37
+ readonly region?: string;
38
+ /**
39
+ * Custom endpoint URL — set for MinIO / R2 / B2 / etc.
40
+ * E.g. `"http://localhost:9000"` for local MinIO.
41
+ */
42
+ readonly endpoint?: string;
43
+ /**
44
+ * MinIO + most non-AWS providers require path-style addressing
45
+ * (`endpoint/bucket/key` rather than `bucket.endpoint/key`).
46
+ * Default: `false` (AWS native).
47
+ */
48
+ readonly forcePathStyle?: boolean;
49
+ /**
50
+ * Static credentials. Omit to use the SDK's default credential chain
51
+ * (env vars, IAM role, ~/.aws/credentials). Recommended for AWS.
52
+ */
53
+ readonly credentials?: {
54
+ readonly accessKeyId: string;
55
+ readonly secretAccessKey: string;
56
+ readonly sessionToken?: string;
57
+ };
58
+ /**
59
+ * Reuse an existing `S3Client` instead of constructing one. Useful when
60
+ * the app already has a configured client (multi-bucket scenarios).
61
+ */
62
+ readonly client?: S3Client;
63
+ /** Optional prefix applied to every key — for namespacing inside a bucket. */
64
+ readonly keyPrefix?: string;
65
+ }
66
+ /** Build an S3-backed Storage adapter. */
67
+ export declare function s3Storage(options: S3StorageOptions): Storage;
68
+ //# sourceMappingURL=storage-s3.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"storage-s3.d.ts","sourceRoot":"","sources":["../src/storage-s3.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA6BG;AAEH,OAAO,EACL,QAAQ,EAQT,MAAM,oBAAoB,CAAC;AAE5B,OAAO,EAEL,KAAK,OAAO,EASb,MAAM,gBAAgB,CAAC;AAExB,MAAM,WAAW,gBAAgB;IAC/B,6BAA6B;IAC7B,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;IACxB,kDAAkD;IAClD,QAAQ,CAAC,MAAM,CAAC,EAAE,MAAM,CAAC;IACzB;;;OAGG;IACH,QAAQ,CAAC,QAAQ,CAAC,EAAE,MAAM,CAAC;IAC3B;;;;OAIG;IACH,QAAQ,CAAC,cAAc,CAAC,EAAE,OAAO,CAAC;IAClC;;;OAGG;IACH,QAAQ,CAAC,WAAW,CAAC,EAAE;QACrB,QAAQ,CAAC,WAAW,EAAM,MAAM,CAAC;QACjC,QAAQ,CAAC,eAAe,EAAE,MAAM,CAAC;QACjC,QAAQ,CAAC,YAAY,CAAC,EAAI,MAAM,CAAC;KAClC,CAAC;IACF;;;OAGG;IACH,QAAQ,CAAC,MAAM,CAAC,EAAE,QAAQ,CAAC;IAC3B,8EAA8E;IAC9E,QAAQ,CAAC,SAAS,CAAC,EAAE,MAAM,CAAC;CAC7B;AAED,0CAA0C;AAC1C,wBAAgB,SAAS,CAAC,OAAO,EAAE,gBAAgB,GAAG,OAAO,CAO5D"}
@@ -0,0 +1,224 @@
1
+ /**
2
+ * `@nwire/storage-s3` — S3-compatible adapter.
3
+ *
4
+ * Works with anything that speaks the S3 API:
5
+ * - AWS S3 (default)
6
+ * - MinIO (set `endpoint` + `forcePathStyle: true`)
7
+ * - Cloudflare R2
8
+ * - Backblaze B2
9
+ * - DigitalOcean Spaces, Wasabi, Linode Object Storage, etc.
10
+ *
11
+ * The adapter is a thin shim over `@aws-sdk/client-s3`:
12
+ * - Translates the Nwire Storage contract to S3 commands.
13
+ * - Maps NotFound errors to `StorageObjectNotFoundError`.
14
+ * - Uses `@aws-sdk/s3-request-presigner` for `url()` (presigned URLs).
15
+ * - Provides a `healthCheck` that calls `HeadBucket` — fast, cheap, exact.
16
+ *
17
+ * import { s3Storage } from "@nwire/storage-s3";
18
+ * import { storagePlugin } from "@nwire/storage";
19
+ *
20
+ * const storage = s3Storage({
21
+ * bucket: "uploads",
22
+ * region: "us-east-1",
23
+ * // MinIO:
24
+ * // endpoint: "http://localhost:9000",
25
+ * // forcePathStyle: true,
26
+ * // credentials: { accessKeyId: "minioadmin", secretAccessKey: "minioadmin" },
27
+ * });
28
+ *
29
+ * defineApp("my-app", { plugins: [storagePlugin({ storage })] });
30
+ */
31
+ import { S3Client, PutObjectCommand, GetObjectCommand, DeleteObjectCommand, HeadObjectCommand, HeadBucketCommand, ListObjectsV2Command, } from "@aws-sdk/client-s3";
32
+ import { getSignedUrl } from "@aws-sdk/s3-request-presigner";
33
+ import { StorageObjectNotFoundError, } from "@nwire/storage";
34
+ /** Build an S3-backed Storage adapter. */
35
+ export function s3Storage(options) {
36
+ const client = options.client ?? buildClient(options);
37
+ const bucket = options.bucket;
38
+ const prefix = options.keyPrefix ?? "";
39
+ const k = (key) => `${prefix}${key}`;
40
+ return new S3Storage(client, bucket, prefix, k);
41
+ }
42
+ function buildClient(options) {
43
+ const config = {};
44
+ if (options.region)
45
+ config.region = options.region;
46
+ if (options.endpoint)
47
+ config.endpoint = options.endpoint;
48
+ if (options.forcePathStyle)
49
+ config.forcePathStyle = options.forcePathStyle;
50
+ if (options.credentials) {
51
+ config.credentials = {
52
+ accessKeyId: options.credentials.accessKeyId,
53
+ secretAccessKey: options.credentials.secretAccessKey,
54
+ sessionToken: options.credentials.sessionToken,
55
+ };
56
+ }
57
+ return new S3Client(config);
58
+ }
59
+ /**
60
+ * Storage implementation backed by S3. Implementation as a class so the
61
+ * shape is easy to extend (`S3Storage` could grab telemetry hooks, custom
62
+ * retry policies, etc. in future).
63
+ */
64
+ class S3Storage {
65
+ client;
66
+ bucket;
67
+ prefix;
68
+ k;
69
+ constructor(client, bucket, prefix, k) {
70
+ this.client = client;
71
+ this.bucket = bucket;
72
+ this.prefix = prefix;
73
+ this.k = k;
74
+ }
75
+ async put(key, body, opts) {
76
+ const Body = toS3Body(body);
77
+ const response = await this.client.send(new PutObjectCommand({
78
+ Bucket: this.bucket,
79
+ Key: this.k(key),
80
+ Body,
81
+ ContentType: opts?.contentType,
82
+ CacheControl: opts?.cacheControl,
83
+ Metadata: opts?.metadata ? { ...opts.metadata } : undefined,
84
+ }));
85
+ return {
86
+ key,
87
+ etag: response.ETag,
88
+ size: typeof Body === "string" ? Buffer.byteLength(Body) : Body.byteLength,
89
+ };
90
+ }
91
+ async get(key) {
92
+ try {
93
+ const response = await this.client.send(new GetObjectCommand({ Bucket: this.bucket, Key: this.k(key) }));
94
+ const body = await streamToBuffer(response.Body);
95
+ return {
96
+ key,
97
+ body,
98
+ contentType: response.ContentType,
99
+ size: response.ContentLength,
100
+ etag: response.ETag,
101
+ metadata: response.Metadata,
102
+ };
103
+ }
104
+ catch (err) {
105
+ throw translateError(err, key);
106
+ }
107
+ }
108
+ async getStream(key) {
109
+ try {
110
+ const response = await this.client.send(new GetObjectCommand({ Bucket: this.bucket, Key: this.k(key) }));
111
+ // The SDK's Body is web Readable | Node Readable | Blob depending on
112
+ // runtime — we narrow to a Node stream via the AWS SDK helper shape.
113
+ const body = response.Body;
114
+ if (!body)
115
+ throw new StorageObjectNotFoundError(key);
116
+ // Node 22+ — the SDK's Body is a Node Readable in our target runtime.
117
+ // The other shapes only show up in the browser/Workers builds we
118
+ // don't support.
119
+ return body;
120
+ }
121
+ catch (err) {
122
+ throw translateError(err, key);
123
+ }
124
+ }
125
+ async delete(key) {
126
+ await this.client.send(new DeleteObjectCommand({ Bucket: this.bucket, Key: this.k(key) }));
127
+ }
128
+ async exists(key) {
129
+ try {
130
+ await this.client.send(new HeadObjectCommand({ Bucket: this.bucket, Key: this.k(key) }));
131
+ return true;
132
+ }
133
+ catch (err) {
134
+ if (isNotFound(err))
135
+ return false;
136
+ throw err;
137
+ }
138
+ }
139
+ async list(prefix, opts) {
140
+ const response = await this.client.send(new ListObjectsV2Command({
141
+ Bucket: this.bucket,
142
+ Prefix: prefix ? this.k(prefix) : this.prefix || undefined,
143
+ MaxKeys: opts?.limit,
144
+ ContinuationToken: opts?.cursor,
145
+ }));
146
+ const stripPrefix = this.prefix
147
+ ? (s) => (s.startsWith(this.prefix) ? s.slice(this.prefix.length) : s)
148
+ : (s) => s;
149
+ const entries = (response.Contents ?? []).map((obj) => ({
150
+ key: stripPrefix(obj.Key ?? ""),
151
+ size: obj.Size,
152
+ modifiedAt: obj.LastModified,
153
+ etag: obj.ETag,
154
+ }));
155
+ return {
156
+ entries,
157
+ nextCursor: response.IsTruncated ? response.NextContinuationToken : undefined,
158
+ };
159
+ }
160
+ async url(key, opts) {
161
+ const expiresIn = opts?.expiresInSeconds ?? 900;
162
+ const method = opts?.method ?? "get";
163
+ const command = method === "put"
164
+ ? new PutObjectCommand({
165
+ Bucket: this.bucket,
166
+ Key: this.k(key),
167
+ ContentType: opts?.contentType,
168
+ })
169
+ : new GetObjectCommand({ Bucket: this.bucket, Key: this.k(key) });
170
+ return getSignedUrl(this.client, command, { expiresIn });
171
+ }
172
+ async healthCheck() {
173
+ await this.client.send(new HeadBucketCommand({ Bucket: this.bucket }));
174
+ }
175
+ async shutdown() {
176
+ this.client.destroy();
177
+ }
178
+ }
179
+ // ─── Helpers ────────────────────────────────────────────────────────
180
+ function toS3Body(body) {
181
+ if (Buffer.isBuffer(body))
182
+ return body;
183
+ if (typeof body === "string")
184
+ return body;
185
+ return body;
186
+ }
187
+ async function streamToBuffer(body) {
188
+ if (!body)
189
+ return Buffer.alloc(0);
190
+ // Node Readable produced by the SDK in Node runtimes.
191
+ if (typeof body[Symbol.asyncIterator] === "function") {
192
+ const chunks = [];
193
+ for await (const c of body) {
194
+ chunks.push(Buffer.isBuffer(c) ? c : Buffer.from(c));
195
+ }
196
+ return Buffer.concat(chunks);
197
+ }
198
+ // Web ReadableStream fallback.
199
+ if (typeof body.getReader === "function") {
200
+ const reader = body.getReader();
201
+ const chunks = [];
202
+ while (true) {
203
+ const { value, done } = await reader.read();
204
+ if (done)
205
+ break;
206
+ if (value)
207
+ chunks.push(value);
208
+ }
209
+ return Buffer.concat(chunks.map((c) => Buffer.from(c)));
210
+ }
211
+ throw new Error("S3 response body shape not recognized");
212
+ }
213
+ function isNotFound(err) {
214
+ if (!err || typeof err !== "object")
215
+ return false;
216
+ const e = err;
217
+ return e.name === "NoSuchKey" || e.name === "NotFound" || e.$metadata?.httpStatusCode === 404;
218
+ }
219
+ function translateError(err, key) {
220
+ if (isNotFound(err))
221
+ return new StorageObjectNotFoundError(key);
222
+ return err;
223
+ }
224
+ //# sourceMappingURL=storage-s3.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"storage-s3.js","sourceRoot":"","sources":["../src/storage-s3.ts"],"names":[],"mappings":"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"}
package/package.json ADDED
@@ -0,0 +1,46 @@
1
+ {
2
+ "name": "@nwire/storage-s3",
3
+ "version": "0.7.0",
4
+ "description": "Nwire — S3 storage adapter. Works with AWS S3, MinIO, Cloudflare R2, Backblaze B2, and anything else speaking the S3 API. Wraps @aws-sdk/client-s3 + @aws-sdk/s3-request-presigner.",
5
+ "keywords": [
6
+ "adapter",
7
+ "b2",
8
+ "minio",
9
+ "nwire",
10
+ "r2",
11
+ "s3",
12
+ "storage"
13
+ ],
14
+ "files": [
15
+ "dist",
16
+ "README.md"
17
+ ],
18
+ "type": "module",
19
+ "main": "./dist/storage-s3.js",
20
+ "types": "./dist/storage-s3.d.ts",
21
+ "exports": {
22
+ ".": {
23
+ "import": "./dist/storage-s3.js",
24
+ "types": "./dist/storage-s3.d.ts"
25
+ }
26
+ },
27
+ "publishConfig": {
28
+ "access": "public"
29
+ },
30
+ "dependencies": {
31
+ "@aws-sdk/client-s3": "^3.700.0",
32
+ "@aws-sdk/s3-request-presigner": "^3.700.0",
33
+ "@nwire/storage": "0.7.0"
34
+ },
35
+ "devDependencies": {
36
+ "@types/node": "^22.19.9",
37
+ "typescript": "^5.9.3",
38
+ "vitest": "^4.1.6",
39
+ "@nwire/test-kit": "0.7.0"
40
+ },
41
+ "scripts": {
42
+ "build": "tsc",
43
+ "dev": "tsc --watch",
44
+ "typecheck": "tsc --noEmit"
45
+ }
46
+ }