@miguelmorales13/nestkit 0.4.0 → 0.5.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.
@@ -0,0 +1,136 @@
1
+ import type { Readable } from 'node:stream';
2
+ /**
3
+ * DI token for the storage adapter. Inject with `@Inject(STORAGE)`.
4
+ *
5
+ * `StoragePort` is the single provider-agnostic interface every adapter (local
6
+ * filesystem, S3/R2, Bunny.net, or a future one) implements. Consumers depend on
7
+ * this port and never on a concrete provider, so switching from R2 to Bunny (or
8
+ * back) is a one-line change in `StorageModule.forRoot(...)` — no call site moves.
9
+ */
10
+ export declare const STORAGE: unique symbol;
11
+ /** Options for a write (`put`/`putStream`/`signedUploadUrl`). */
12
+ export interface PutOptions {
13
+ /** MIME type stored with the object (`image/webp`, `application/pdf`, `video/mp4`, …). */
14
+ contentType?: string;
15
+ /** `Cache-Control` header the object is served with (e.g. `public, max-age=31536000, immutable`). */
16
+ cacheControl?: string;
17
+ /** `Content-Disposition` (e.g. `attachment; filename="factura.pdf"`) for downloads. */
18
+ contentDisposition?: string;
19
+ /** Arbitrary metadata, stored where the provider supports it. */
20
+ metadata?: Record<string, string>;
21
+ /**
22
+ * Byte length of the payload. Optional for `put` (a Buffer knows its size) but
23
+ * some providers require it up front when streaming a body of unknown length.
24
+ */
25
+ contentLength?: number;
26
+ }
27
+ /** What is known about a stored object. */
28
+ export interface ObjectInfo {
29
+ key: string;
30
+ /** Size in bytes. */
31
+ size: number;
32
+ contentType?: string;
33
+ lastModified?: Date;
34
+ etag?: string;
35
+ }
36
+ /**
37
+ * A half-open-ish byte range, HTTP semantics: `start` and `end` are both
38
+ * INCLUSIVE. `{ start: 0, end: 1023 }` is the first 1024 bytes. `end` omitted
39
+ * means "to the end of the object". Used to serve video/audio seek requests.
40
+ */
41
+ export interface ByteRange {
42
+ start: number;
43
+ end?: number;
44
+ }
45
+ /** The result of opening an object for reading as a stream. */
46
+ export interface ReadStream {
47
+ stream: Readable;
48
+ contentType?: string;
49
+ /** Bytes in THIS response (the range length when a range was requested). */
50
+ contentLength?: number;
51
+ /** Total size of the whole object, regardless of range. */
52
+ totalSize?: number;
53
+ /** Present only when a range was served: the `Content-Range` header value. */
54
+ contentRange?: string;
55
+ lastModified?: Date;
56
+ etag?: string;
57
+ }
58
+ /**
59
+ * A short-lived target a browser (or any client) can upload to DIRECTLY, without
60
+ * the file passing through the API. Essential for large files (video): the API
61
+ * hands back this target, the client PUTs/POSTs the bytes straight to the
62
+ * provider, and the API is never a proxy for gigabytes.
63
+ */
64
+ export interface SignedUploadTarget {
65
+ /** URL to upload to. */
66
+ url: string;
67
+ /** HTTP method to use. `PUT` for presigned-URL providers; `POST` for form-policy uploads. */
68
+ method: 'PUT' | 'POST';
69
+ /** Headers the client MUST send (e.g. `Content-Type`) for the signature to match. */
70
+ headers?: Record<string, string>;
71
+ /** For `POST` form uploads (S3 POST policy): fields to include in the multipart form before the file. */
72
+ fields?: Record<string, string>;
73
+ /** The key the object will land at. */
74
+ key: string;
75
+ /** When the target stops being valid. */
76
+ expiresAt: Date;
77
+ }
78
+ /** A page of `list()` results. */
79
+ export interface ListResult {
80
+ objects: ObjectInfo[];
81
+ /** Opaque cursor for the next page; absent when there are no more. */
82
+ cursor?: string;
83
+ }
84
+ /**
85
+ * Provider-agnostic file storage. Handles any file type — images, PDFs and other
86
+ * documents, video, audio, archives — the port makes no assumption about content.
87
+ * Adapters: local filesystem (dev), S3-compatible (AWS S3 and Cloudflare R2), and
88
+ * Bunny.net.
89
+ *
90
+ * Keys are forward-slash paths WITHOUT a leading slash, e.g.
91
+ * `tenant-123/videos/clip.mp4`. The caller composes keys; never build one from an
92
+ * untrusted filename (path traversal). Adapters store objects verbatim under the key.
93
+ */
94
+ export interface StoragePort {
95
+ /** Store `data` at `key`, overwriting if it exists. Returns what is known about the stored object. */
96
+ put(key: string, data: Buffer | Uint8Array, options?: PutOptions): Promise<ObjectInfo>;
97
+ /**
98
+ * Store a readable stream at `key` (large uploads, video). The adapter uses
99
+ * multipart under the hood where the provider needs it, so the whole file never
100
+ * has to sit in memory.
101
+ */
102
+ putStream(key: string, stream: Readable, options?: PutOptions): Promise<ObjectInfo>;
103
+ /** Read the whole object into memory. Prefer `getStream` for anything large. */
104
+ get(key: string): Promise<Buffer>;
105
+ /** Open the object as a stream, optionally a byte range (for seeking media). */
106
+ getStream(key: string, range?: ByteRange): Promise<ReadStream>;
107
+ /** Metadata about the object, or `null` if it does not exist. */
108
+ stat(key: string): Promise<ObjectInfo | null>;
109
+ /** Whether an object exists at `key`. */
110
+ exists(key: string): Promise<boolean>;
111
+ /** Delete the object. Idempotent: deleting a missing key does not throw. */
112
+ delete(key: string): Promise<void>;
113
+ /** List objects under `prefix`, paginated. */
114
+ list(prefix: string, options?: {
115
+ limit?: number;
116
+ cursor?: string;
117
+ }): Promise<ListResult>;
118
+ /**
119
+ * A short-lived URL to READ a private object. For a public bucket/zone prefer
120
+ * `publicUrl`. `ttlSeconds` defaults to one hour.
121
+ */
122
+ signedReadUrl(key: string, ttlSeconds?: number): Promise<string>;
123
+ /**
124
+ * A short-lived target for a client to UPLOAD directly to the provider.
125
+ * Not every provider supports this the same way — see each adapter.
126
+ */
127
+ signedUploadUrl(key: string, options?: PutOptions & {
128
+ ttlSeconds?: number;
129
+ }): Promise<SignedUploadTarget>;
130
+ /**
131
+ * The stable public URL for `key`, or `null` when the store is private (no
132
+ * public base URL / pull zone configured). For private stores, use
133
+ * `signedReadUrl` instead.
134
+ */
135
+ publicUrl(key: string): string | null;
136
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@miguelmorales13/nestkit",
3
- "version": "0.4.0",
3
+ "version": "0.5.0",
4
4
  "private": false,
5
5
  "license": "MIT",
6
6
  "type": "module",
@@ -110,6 +110,11 @@
110
110
  "types": "./dist/media/index.d.ts",
111
111
  "import": "./dist/media/index.js",
112
112
  "require": "./dist/media/index.cjs"
113
+ },
114
+ "./storage": {
115
+ "types": "./dist/storage/index.d.ts",
116
+ "import": "./dist/storage/index.js",
117
+ "require": "./dist/storage/index.cjs"
113
118
  }
114
119
  },
115
120
  "scripts": {
@@ -131,9 +136,21 @@
131
136
  "class-validator": "^0.15.0",
132
137
  "class-transformer": "^0.5.1",
133
138
  "resend": "^6.20.0",
134
- "nodemailer": "^9.0.5"
139
+ "nodemailer": "^9.0.5",
140
+ "@aws-sdk/client-s3": "^3.0.0",
141
+ "@aws-sdk/s3-request-presigner": "^3.0.0",
142
+ "@aws-sdk/lib-storage": "^3.0.0"
135
143
  },
136
144
  "peerDependenciesMeta": {
145
+ "@aws-sdk/client-s3": {
146
+ "optional": true
147
+ },
148
+ "@aws-sdk/s3-request-presigner": {
149
+ "optional": true
150
+ },
151
+ "@aws-sdk/lib-storage": {
152
+ "optional": true
153
+ },
137
154
  "pg": {
138
155
  "optional": true
139
156
  },
@@ -171,6 +188,9 @@
171
188
  "jsonwebtoken": "^9.0.3"
172
189
  },
173
190
  "devDependencies": {
191
+ "@aws-sdk/client-s3": "^3.1121.0",
192
+ "@aws-sdk/lib-storage": "^3.1121.0",
193
+ "@aws-sdk/s3-request-presigner": "^3.1121.0",
174
194
  "@nestjs/common": "^11.0.0",
175
195
  "@nestjs/core": "^11.0.0",
176
196
  "@nestjs/swagger": "^11.0.0",