@nwire/storage 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,71 @@
1
+ # @nwire/storage
2
+
3
+ > Object-storage contract — narrow `Storage` interface (`put`/`get`/`stream`/`url`/`list`/`delete`).
4
+
5
+ ## What it does
6
+
7
+ Defines the `Storage` interface every adapter implements and ships `storagePlugin({ storage })` that registers it on the container with lifecycle hooks. The contract is intentionally narrow — what 95% of apps actually need. Adapters that can't honor a method (e.g. `fs` has no presigned URLs) throw `StorageUnsupportedError` so callers can branch on capability.
8
+
9
+ ## Install
10
+
11
+ ```bash
12
+ pnpm add @nwire/storage
13
+ ```
14
+
15
+ ## Quick start
16
+
17
+ ```ts
18
+ import { storagePlugin, InMemoryStorage } from "@nwire/storage";
19
+ import { defineApp, defineAction } from "@nwire/forge";
20
+
21
+ const storage = new InMemoryStorage();
22
+ defineApp("my-app", { plugins: [storagePlugin({ storage })] });
23
+
24
+ defineAction({
25
+ name: "uploads.create",
26
+ handler: async ({ input }, ctx) => {
27
+ const s = ctx.resolve<typeof storage>("storage");
28
+ await s.put(input.key, input.bytes, { contentType: input.contentType });
29
+ return { url: await s.url(input.key) };
30
+ },
31
+ });
32
+ ```
33
+
34
+ ## API surface
35
+
36
+ - `Storage` — `put` / `get` / `getStream` / `delete` / `exists` / `list` / `url`.
37
+ - `storagePlugin({ storage })` — register on container; lifecycle-managed.
38
+ - `InMemoryStorage` — captures bytes in a Map; useful in tests.
39
+ - `StorageObjectNotFoundError` / `StorageUnsupportedError`.
40
+
41
+ ## When to use
42
+
43
+ Any app that handles files (uploads, exports, generated reports). Fits L3 and up.
44
+
45
+ ## Standalone use
46
+
47
+ For developers using `@nwire/storage` **without the rest of Nwire** — pair it with any TypeScript project, any container, any HTTP framework.
48
+
49
+ ```ts
50
+ // See the package's main entry (src/) for the standalone surface.
51
+ // The exports below work without @nwire/app or @nwire/forge.
52
+ import {} from /* ...standalone exports... */ "@nwire/storage";
53
+ ```
54
+
55
+ ## Within nwire-app
56
+
57
+ 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 })`.
58
+
59
+ ```ts
60
+ import { createApp } from "@nwire/forge";
61
+
62
+ const app = createApp({
63
+ /* ...config... */
64
+ });
65
+ // Adapter/plugin wiring happens here when applicable.
66
+ ```
67
+
68
+ ## See also
69
+
70
+ - [Architecture sketch §05 — Adapters tier](../../architecture-sketch.html#packages)
71
+ - Sibling packages: [@nwire/storage-s3](../nwire-storage-s3), [@nwire/storage-fs](../nwire-storage-fs)
@@ -0,0 +1,8 @@
1
+ /**
2
+ * `@nwire/storage` — contract verification via InMemoryStorage.
3
+ *
4
+ * The same test set should run against every adapter (S3/MinIO/fs)
5
+ * with minimal changes — verifies the contract is uniform.
6
+ */
7
+ export {};
8
+ //# sourceMappingURL=storage.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"storage.test.d.ts","sourceRoot":"","sources":["../../src/__tests__/storage.test.ts"],"names":[],"mappings":"AAAA;;;;;GAKG"}
@@ -0,0 +1,97 @@
1
+ /**
2
+ * `@nwire/storage` — contract verification via InMemoryStorage.
3
+ *
4
+ * The same test set should run against every adapter (S3/MinIO/fs)
5
+ * with minimal changes — verifies the contract is uniform.
6
+ */
7
+ import { describe, it, expect } from "vitest";
8
+ import { InMemoryStorage, StorageObjectNotFoundError, storagePlugin, } from "../storage";
9
+ import { createApp } from "@nwire/forge";
10
+ describe("InMemoryStorage", () => {
11
+ it("put + get round-trips bytes with metadata", async () => {
12
+ const s = new InMemoryStorage();
13
+ const result = await s.put("a/b.txt", "hello", {
14
+ contentType: "text/plain",
15
+ metadata: { uploader: "alice" },
16
+ });
17
+ expect(result.key).toBe("a/b.txt");
18
+ expect(result.size).toBe(5);
19
+ const obj = await s.get("a/b.txt");
20
+ expect(obj.body.toString("utf-8")).toBe("hello");
21
+ expect(obj.contentType).toBe("text/plain");
22
+ expect(obj.metadata?.uploader).toBe("alice");
23
+ });
24
+ it("get throws StorageObjectNotFoundError for unknown keys", async () => {
25
+ const s = new InMemoryStorage();
26
+ await expect(s.get("nope.txt")).rejects.toBeInstanceOf(StorageObjectNotFoundError);
27
+ });
28
+ it("exists reflects put + delete", async () => {
29
+ const s = new InMemoryStorage();
30
+ expect(await s.exists("x")).toBe(false);
31
+ await s.put("x", "");
32
+ expect(await s.exists("x")).toBe(true);
33
+ await s.delete("x");
34
+ expect(await s.exists("x")).toBe(false);
35
+ });
36
+ it("getStream produces a readable for the same body", async () => {
37
+ const s = new InMemoryStorage();
38
+ await s.put("stream.txt", "abc");
39
+ const stream = await s.getStream("stream.txt");
40
+ const chunks = [];
41
+ for await (const c of stream)
42
+ chunks.push(c);
43
+ expect(Buffer.concat(chunks).toString("utf-8")).toBe("abc");
44
+ });
45
+ it("list filters by prefix and is paginated", async () => {
46
+ const s = new InMemoryStorage();
47
+ for (const k of ["users/a", "users/b", "users/c", "posts/x"]) {
48
+ await s.put(k, k);
49
+ }
50
+ const page1 = await s.list("users/", { limit: 2 });
51
+ expect(page1.entries.map((e) => e.key)).toEqual(["users/a", "users/b"]);
52
+ expect(page1.nextCursor).toBe("users/b");
53
+ const page2 = await s.list("users/", { limit: 2, cursor: page1.nextCursor });
54
+ expect(page2.entries.map((e) => e.key)).toEqual(["users/c"]);
55
+ expect(page2.nextCursor).toBeUndefined();
56
+ });
57
+ it("url returns a memory:// URL for the test adapter", async () => {
58
+ const s = new InMemoryStorage();
59
+ await s.put("avatar.png", "x");
60
+ const url = await s.url("avatar.png");
61
+ expect(url).toMatch(/^memory:\/\/avatar/);
62
+ });
63
+ });
64
+ describe("storagePlugin", () => {
65
+ it("boots + stops cleanly with the default InMemory adapter", async () => {
66
+ const app = createApp({ modules: [], plugins: [storagePlugin()] });
67
+ await app.start();
68
+ await app.stop();
69
+ });
70
+ it("calls adapter.shutdown on app.stop when defined", async () => {
71
+ let stopped = false;
72
+ const adapter = {
73
+ put: async () => ({ key: "x" }),
74
+ get: async () => {
75
+ throw new Error("not used");
76
+ },
77
+ getStream: async () => {
78
+ throw new Error("not used");
79
+ },
80
+ delete: async () => { },
81
+ exists: async () => false,
82
+ list: async () => ({ entries: [] }),
83
+ url: async () => "x://",
84
+ shutdown: async () => {
85
+ stopped = true;
86
+ },
87
+ };
88
+ const app = createApp({
89
+ modules: [],
90
+ plugins: [storagePlugin({ storage: adapter, name: "files" })],
91
+ });
92
+ await app.start();
93
+ await app.stop();
94
+ expect(stopped).toBe(true);
95
+ });
96
+ });
97
+ //# sourceMappingURL=storage.test.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"storage.test.js","sourceRoot":"","sources":["../../src/__tests__/storage.test.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,165 @@
1
+ /**
2
+ * `@nwire/storage` — object-storage contract.
3
+ *
4
+ * One interface every adapter implements; the framework "owns" the
5
+ * lifecycle (boot, health check, shutdown) via `storagePlugin`. Domain
6
+ * code calls `ctx.resolve<Storage>("storage")` (or whatever name the
7
+ * plugin was given) and gets the real adapter back.
8
+ *
9
+ * - `@nwire/storage-s3` — S3 + MinIO + R2 + B2 (anything S3-API)
10
+ * - `@nwire/storage-fs` — local filesystem (dev / single-node)
11
+ *
12
+ * The contract is intentionally narrow — what 95% of apps actually need:
13
+ * - put(key, bytes, opts?) store an object
14
+ * - get(key) retrieve as a buffer
15
+ * - getStream(key) retrieve as a Node stream (large files)
16
+ * - delete(key) remove
17
+ * - exists(key) existence check
18
+ * - list(prefix?, opts?) paginated listing
19
+ * - url(key, opts?) presigned URL for client uploads/downloads
20
+ *
21
+ * Adapters that can't honor a method (e.g., fs has no presigned URLs in
22
+ * the same sense) MAY throw `StorageUnsupportedError` from it; callers
23
+ * branch on capability when this matters.
24
+ */
25
+ import { type PluginDefinition } from "@nwire/forge";
26
+ /** Bytes you can write — Buffer, Uint8Array, or string (utf-8 encoded). */
27
+ export type StorageBody = Buffer | Uint8Array | string;
28
+ /** Options accepted by `put`. */
29
+ export interface StoragePutOptions {
30
+ /** MIME type. Defaults to `"application/octet-stream"`. */
31
+ readonly contentType?: string;
32
+ /**
33
+ * Cache-Control header value the adapter should set on the object.
34
+ * Adapters that don't model HTTP headers (fs) ignore this.
35
+ */
36
+ readonly cacheControl?: string;
37
+ /** User-defined metadata key/value pairs. Adapters preserve as best they can. */
38
+ readonly metadata?: Readonly<Record<string, string>>;
39
+ }
40
+ /** Result of a successful `put`. */
41
+ export interface StoragePutResult {
42
+ readonly key: string;
43
+ readonly etag?: string;
44
+ readonly size?: number;
45
+ }
46
+ /** Result of `get` — bytes + the metadata the adapter knows about. */
47
+ export interface StorageObject {
48
+ readonly key: string;
49
+ readonly body: Buffer;
50
+ readonly contentType?: string;
51
+ readonly size?: number;
52
+ readonly etag?: string;
53
+ readonly metadata?: Readonly<Record<string, string>>;
54
+ }
55
+ /** A single entry returned by `list`. */
56
+ export interface StorageEntry {
57
+ readonly key: string;
58
+ readonly size?: number;
59
+ readonly modifiedAt?: Date;
60
+ readonly etag?: string;
61
+ }
62
+ /** Options accepted by `list`. */
63
+ export interface StorageListOptions {
64
+ /** Max items per page. Adapter-default if omitted. */
65
+ readonly limit?: number;
66
+ /** Continuation token from a previous page. */
67
+ readonly cursor?: string;
68
+ }
69
+ /** Result page of `list`. */
70
+ export interface StorageListPage {
71
+ readonly entries: readonly StorageEntry[];
72
+ /** Present when more pages exist; pass back as `cursor`. */
73
+ readonly nextCursor?: string;
74
+ }
75
+ /** Options for `url`. */
76
+ export interface StorageUrlOptions {
77
+ /** GET (download) or PUT (upload). Default: `"get"`. */
78
+ readonly method?: "get" | "put";
79
+ /** Expiry in seconds. Default: 900 (15 min). */
80
+ readonly expiresInSeconds?: number;
81
+ /** For `method:"put"` — required content-type. */
82
+ readonly contentType?: string;
83
+ }
84
+ /**
85
+ * The contract every adapter implements. Stable across S3/MinIO/fs/etc.
86
+ */
87
+ export interface Storage {
88
+ put(key: string, body: StorageBody, opts?: StoragePutOptions): Promise<StoragePutResult>;
89
+ get(key: string): Promise<StorageObject>;
90
+ getStream(key: string): Promise<NodeJS.ReadableStream>;
91
+ delete(key: string): Promise<void>;
92
+ exists(key: string): Promise<boolean>;
93
+ list(prefix?: string, opts?: StorageListOptions): Promise<StorageListPage>;
94
+ url(key: string, opts?: StorageUrlOptions): Promise<string>;
95
+ /** Optional readiness probe — adapter checks its backend is reachable. */
96
+ healthCheck?(): Promise<void>;
97
+ /** Optional shutdown — close clients, flush pending uploads, etc. */
98
+ shutdown?(): Promise<void>;
99
+ }
100
+ /**
101
+ * Thrown when an object key isn't present. Adapters MUST translate
102
+ * backend "not found" into this — never expose raw S3 / fs errors.
103
+ */
104
+ export declare class StorageObjectNotFoundError extends Error {
105
+ readonly $kind: "storage.object-not-found";
106
+ readonly status: 404;
107
+ readonly key: string;
108
+ constructor(key: string);
109
+ }
110
+ /** Thrown when a method isn't supported by the adapter (rare). */
111
+ export declare class StorageUnsupportedError extends Error {
112
+ readonly $kind: "storage.unsupported";
113
+ readonly status: 501;
114
+ constructor(message?: string);
115
+ }
116
+ /**
117
+ * The default — keeps every put in a Map. Test-grade only. Resets when
118
+ * the process restarts; not suitable for anything other than tests.
119
+ *
120
+ * const storage = new InMemoryStorage()
121
+ * await storage.put("avatar.png", Buffer.from([...]))
122
+ * const obj = await storage.get("avatar.png")
123
+ */
124
+ export declare class InMemoryStorage implements Storage {
125
+ private readonly store;
126
+ put(key: string, body: StorageBody, opts?: StoragePutOptions): Promise<StoragePutResult>;
127
+ get(key: string): Promise<StorageObject>;
128
+ getStream(key: string): Promise<NodeJS.ReadableStream>;
129
+ delete(key: string): Promise<void>;
130
+ exists(key: string): Promise<boolean>;
131
+ list(prefix?: string, opts?: StorageListOptions): Promise<StorageListPage>;
132
+ /** Returns a memory:// URL — for tests only; not actually fetchable. */
133
+ url(key: string, _opts?: StorageUrlOptions): Promise<string>;
134
+ /** Diagnostic — peek at what's stored. Not in the contract. */
135
+ size(): number;
136
+ /** Diagnostic — clear everything (tests). Not in the contract. */
137
+ clear(): void;
138
+ }
139
+ export interface StoragePluginOptions {
140
+ /**
141
+ * The Storage adapter to register. Default: `new InMemoryStorage()` —
142
+ * test-grade only; configure a real adapter in production.
143
+ */
144
+ readonly storage?: Storage;
145
+ /**
146
+ * Name the storage is registered under in the container. Default:
147
+ * `"storage"`. Use a distinct name when serving multiple stores
148
+ * (e.g. `"avatars"`, `"invoices"`).
149
+ */
150
+ readonly name?: string;
151
+ }
152
+ /**
153
+ * Build a Nwire plugin that registers a `Storage` adapter, wires its
154
+ * health check into readiness, and shuts it down cleanly. Use whichever
155
+ * adapter package fits the deployment:
156
+ *
157
+ * import { storagePlugin } from "@nwire/storage"
158
+ * import { s3Storage } from "@nwire/storage-s3"
159
+ *
160
+ * defineApp("my-app", {
161
+ * plugins: [storagePlugin({ storage: s3Storage({...}) })],
162
+ * })
163
+ */
164
+ export declare function storagePlugin(options?: StoragePluginOptions): PluginDefinition;
165
+ //# sourceMappingURL=storage.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"storage.d.ts","sourceRoot":"","sources":["../src/storage.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,167 @@
1
+ /**
2
+ * `@nwire/storage` — object-storage contract.
3
+ *
4
+ * One interface every adapter implements; the framework "owns" the
5
+ * lifecycle (boot, health check, shutdown) via `storagePlugin`. Domain
6
+ * code calls `ctx.resolve<Storage>("storage")` (or whatever name the
7
+ * plugin was given) and gets the real adapter back.
8
+ *
9
+ * - `@nwire/storage-s3` — S3 + MinIO + R2 + B2 (anything S3-API)
10
+ * - `@nwire/storage-fs` — local filesystem (dev / single-node)
11
+ *
12
+ * The contract is intentionally narrow — what 95% of apps actually need:
13
+ * - put(key, bytes, opts?) store an object
14
+ * - get(key) retrieve as a buffer
15
+ * - getStream(key) retrieve as a Node stream (large files)
16
+ * - delete(key) remove
17
+ * - exists(key) existence check
18
+ * - list(prefix?, opts?) paginated listing
19
+ * - url(key, opts?) presigned URL for client uploads/downloads
20
+ *
21
+ * Adapters that can't honor a method (e.g., fs has no presigned URLs in
22
+ * the same sense) MAY throw `StorageUnsupportedError` from it; callers
23
+ * branch on capability when this matters.
24
+ */
25
+ import { definePlugin } from "@nwire/forge";
26
+ // ─── Errors ────────────────────────────────────────────────────────
27
+ /**
28
+ * Thrown when an object key isn't present. Adapters MUST translate
29
+ * backend "not found" into this — never expose raw S3 / fs errors.
30
+ */
31
+ export class StorageObjectNotFoundError extends Error {
32
+ $kind = "storage.object-not-found";
33
+ status = 404;
34
+ key;
35
+ constructor(key) {
36
+ super(`No object at "${key}"`);
37
+ this.name = "StorageObjectNotFoundError";
38
+ this.key = key;
39
+ }
40
+ }
41
+ /** Thrown when a method isn't supported by the adapter (rare). */
42
+ export class StorageUnsupportedError extends Error {
43
+ $kind = "storage.unsupported";
44
+ status = 501;
45
+ constructor(message = "storage method not supported by this adapter") {
46
+ super(message);
47
+ this.name = "StorageUnsupportedError";
48
+ }
49
+ }
50
+ /**
51
+ * The default — keeps every put in a Map. Test-grade only. Resets when
52
+ * the process restarts; not suitable for anything other than tests.
53
+ *
54
+ * const storage = new InMemoryStorage()
55
+ * await storage.put("avatar.png", Buffer.from([...]))
56
+ * const obj = await storage.get("avatar.png")
57
+ */
58
+ export class InMemoryStorage {
59
+ store = new Map();
60
+ async put(key, body, opts) {
61
+ const buf = toBuffer(body);
62
+ const rec = {
63
+ body: buf,
64
+ contentType: opts?.contentType ?? "application/octet-stream",
65
+ cacheControl: opts?.cacheControl,
66
+ metadata: opts?.metadata,
67
+ modifiedAt: new Date(),
68
+ etag: simpleEtag(buf),
69
+ };
70
+ this.store.set(key, rec);
71
+ return { key, etag: rec.etag, size: buf.byteLength };
72
+ }
73
+ async get(key) {
74
+ const rec = this.store.get(key);
75
+ if (!rec)
76
+ throw new StorageObjectNotFoundError(key);
77
+ return {
78
+ key,
79
+ body: rec.body,
80
+ contentType: rec.contentType,
81
+ size: rec.body.byteLength,
82
+ etag: rec.etag,
83
+ metadata: rec.metadata,
84
+ };
85
+ }
86
+ async getStream(key) {
87
+ const obj = await this.get(key);
88
+ const { Readable } = await import("node:stream");
89
+ return Readable.from(obj.body);
90
+ }
91
+ async delete(key) {
92
+ this.store.delete(key);
93
+ }
94
+ async exists(key) {
95
+ return this.store.has(key);
96
+ }
97
+ async list(prefix, opts) {
98
+ const limit = opts?.limit ?? 1000;
99
+ const cursor = opts?.cursor;
100
+ const all = Array.from(this.store.entries())
101
+ .filter(([k]) => !prefix || k.startsWith(prefix))
102
+ .sort(([a], [b]) => (a < b ? -1 : a > b ? 1 : 0));
103
+ const startAt = cursor ? all.findIndex(([k]) => k > cursor) : 0;
104
+ const slice = all.slice(Math.max(0, startAt), Math.max(0, startAt) + limit);
105
+ const entries = slice.map(([key, rec]) => ({
106
+ key,
107
+ size: rec.body.byteLength,
108
+ modifiedAt: rec.modifiedAt,
109
+ etag: rec.etag,
110
+ }));
111
+ const last = entries.at(-1)?.key;
112
+ const more = last && all.findIndex(([k]) => k === last) < all.length - 1;
113
+ return { entries, nextCursor: more ? last : undefined };
114
+ }
115
+ /** Returns a memory:// URL — for tests only; not actually fetchable. */
116
+ async url(key, _opts) {
117
+ return `memory://${encodeURIComponent(key)}`;
118
+ }
119
+ /** Diagnostic — peek at what's stored. Not in the contract. */
120
+ size() {
121
+ return this.store.size;
122
+ }
123
+ /** Diagnostic — clear everything (tests). Not in the contract. */
124
+ clear() {
125
+ this.store.clear();
126
+ }
127
+ }
128
+ function toBuffer(body) {
129
+ if (Buffer.isBuffer(body))
130
+ return body;
131
+ if (typeof body === "string")
132
+ return Buffer.from(body, "utf-8");
133
+ return Buffer.from(body);
134
+ }
135
+ /** djb2 hash + length — cheap, deterministic, good enough for tests. */
136
+ function simpleEtag(buf) {
137
+ let h = 5381;
138
+ for (let i = 0; i < buf.length; i++)
139
+ h = ((h << 5) + h) ^ buf[i];
140
+ return `"${(h >>> 0).toString(16)}-${buf.length}"`;
141
+ }
142
+ /**
143
+ * Build a Nwire plugin that registers a `Storage` adapter, wires its
144
+ * health check into readiness, and shuts it down cleanly. Use whichever
145
+ * adapter package fits the deployment:
146
+ *
147
+ * import { storagePlugin } from "@nwire/storage"
148
+ * import { s3Storage } from "@nwire/storage-s3"
149
+ *
150
+ * defineApp("my-app", {
151
+ * plugins: [storagePlugin({ storage: s3Storage({...}) })],
152
+ * })
153
+ */
154
+ export function storagePlugin(options = {}) {
155
+ const storage = options.storage ?? new InMemoryStorage();
156
+ const name = options.name ?? "storage";
157
+ return definePlugin(`storage:${name}`, ({ bind, shutdown }) => {
158
+ bind(name, () => storage);
159
+ const stop = storage.shutdown;
160
+ if (stop) {
161
+ shutdown(async () => {
162
+ await stop.call(storage);
163
+ });
164
+ }
165
+ });
166
+ }
167
+ //# sourceMappingURL=storage.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"storage.js","sourceRoot":"","sources":["../src/storage.ts"],"names":[],"mappings":"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"}
package/package.json ADDED
@@ -0,0 +1,42 @@
1
+ {
2
+ "name": "@nwire/storage",
3
+ "version": "0.7.0",
4
+ "description": "Nwire — object-storage contract + InMemoryStorage default + storagePlugin. Adapters (S3, filesystem) live in separate packages (@nwire/storage-s3, @nwire/storage-fs).",
5
+ "keywords": [
6
+ "adapter",
7
+ "blob",
8
+ "nwire",
9
+ "object-storage",
10
+ "s3",
11
+ "storage"
12
+ ],
13
+ "files": [
14
+ "dist",
15
+ "README.md"
16
+ ],
17
+ "type": "module",
18
+ "main": "./dist/storage.js",
19
+ "types": "./dist/storage.d.ts",
20
+ "exports": {
21
+ ".": {
22
+ "import": "./dist/storage.js",
23
+ "types": "./dist/storage.d.ts"
24
+ }
25
+ },
26
+ "publishConfig": {
27
+ "access": "public"
28
+ },
29
+ "dependencies": {
30
+ "@nwire/forge": "0.7.0"
31
+ },
32
+ "devDependencies": {
33
+ "@types/node": "^22.19.9",
34
+ "typescript": "^5.9.3",
35
+ "vitest": "^4.1.6"
36
+ },
37
+ "scripts": {
38
+ "build": "tsc",
39
+ "dev": "tsc --watch",
40
+ "typecheck": "tsc --noEmit"
41
+ }
42
+ }