@telestack/storage 1.0.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/README.md ADDED
@@ -0,0 +1,132 @@
1
+ # TelestackStorage Web SDK
2
+
3
+ A powerful, fluent, and comprehensive client SDK for TelestackStorage.
4
+ Designed expressly to be **faster, more capable, and easier to use than Firebase Storage or Appwrite Storage.**
5
+
6
+ ## Features (Firebase Killer)
7
+
8
+ - 🌊 **Fluent Directory & File References** (`storage.ref('path').child('video.mp4')`)
9
+ - 📈 **Observable Upload Tasks**: Built directly around progress observers, pausing, resuming, and canceling `uploadTask.on('state_changed', ...)`
10
+ - 🚀 **Automatic Chunked Resumable Uploads**: Flawlessly transitions streams to chunked multipart uploads (perfect for poor connectivity)
11
+ - 🗂️ **Rich Querying**: Full-text and metadata array queries (`query().where('uid', '123').get()`)
12
+ - 📦 **Mass Batch Operations**: Instantly delete, copy, or move multiple files over network in _one_ request.
13
+ - 🛡️ **Enterprise Ready**: Storage Versioning, Legal holds, and Object Tagging out of the box.
14
+ - 🌐 **Zero Dependencies**, fully browser native `fetch` and `XMLHttpRequest`.
15
+
16
+ ---
17
+
18
+ ## 🚀 Standalone Installation (CDN)
19
+
20
+ You can use Telestack Storage as a completely standalone library directly in HTML:
21
+
22
+ ```html
23
+ <script src="https://unpkg.com/@telestack/storage/dist/index.global.js"></script>
24
+ <script>
25
+ // The SDK is exposed globally as `TelestackStorageSetup.TelestackStorage`
26
+ const { TelestackStorage } = window.TelestackStorageSetup;
27
+
28
+ const storage = new TelestackStorage({
29
+ baseUrl: 'https://storage.yourdomain.com',
30
+ tenantId: 'your_tenant_id',
31
+ apiKey: 'your_api_key'
32
+ });
33
+ </script>
34
+ ```
35
+
36
+ ---
37
+
38
+ ## 📦 NPM Installation
39
+
40
+ ```bash
41
+ npm install @telestack/storage
42
+ ```
43
+
44
+ ---
45
+
46
+ ## Supercharged Usage
47
+
48
+ ### 1. Observable Uploads (Pause, Resume, Cancel)
49
+
50
+ Works exactly like Firebase, but intelligently handles chunking for massive files automatically.
51
+
52
+ ```typescript
53
+ import { TelestackStorage } from '@telestack/storage';
54
+
55
+ const storage = new TelestackStorage({
56
+ baseUrl: 'https://api.yourdomain.com',
57
+ tenantId: 'tenant_123'
58
+ });
59
+
60
+ const videoBlob = new Blob([...]);
61
+ const task = storage.ref('videos/concert.mp4').put(videoBlob, { userId: 'u123' });
62
+
63
+ // Listen for state changes, errors, and completion of the upload.
64
+ const unsubscribe = task.on('state_changed',
65
+ (snapshot) => {
66
+ // Observers receive exact byte counts and state ('running', 'paused', etc.)
67
+ const progress = (snapshot.bytesTransferred / snapshot.totalBytes) * 100;
68
+ console.log(`Upload is ${progress}% done. State: ${snapshot.state}`);
69
+ },
70
+ (error) => {
71
+ console.error('Upload failed:', error);
72
+ },
73
+ () => {
74
+ console.log('Upload completed successfully!');
75
+ // Get the download URL directly from the ref
76
+ task.snapshot.task.ref.getDownloadUrl().then(url => console.log('File available at:', url));
77
+ }
78
+ );
79
+
80
+ // Control it!
81
+ task.pause();
82
+ task.resume();
83
+ task.cancel();
84
+ ```
85
+
86
+ ### 2. Fluent Query Builder (Search & Filtering)
87
+
88
+ No more paginating basic lists manually to find a single file. Search by name or filter by metadata effortlessly:
89
+
90
+ ```typescript
91
+ const files = await storage.query()
92
+ .nameContains('report')
93
+ .where('project', 'Q1-Metrics')
94
+ .limit(20)
95
+ .get();
96
+ ```
97
+
98
+ ### 3. Server-Side Batch Operations
99
+
100
+ Perform mass directory operations securely with a single API call (executed asynchronously on Cloudflare):
101
+
102
+ ```typescript
103
+ // Delete multiple files instantly
104
+ await storage.batch()
105
+ .delete(['old/1.png', 'old/2.png'])
106
+ .run();
107
+
108
+ // Move/Rename hundreds of files effortlessly
109
+ await storage.batch()
110
+ .move(['docs/a.pdf', 'docs/b.pdf'], 'archive/')
111
+ .run();
112
+ ```
113
+
114
+ ### 4. Enterprise Compliance Features
115
+
116
+ Storage versioning, tagging & immutable legal holds are built natively into the fluent refs:
117
+
118
+ ```typescript
119
+ const docRef = storage.ref('critical-contracts/nda.pdf');
120
+
121
+ // Retrieve a previously overwritten historical version
122
+ const url = await docRef.getVersionUrl('v3_abc123');
123
+
124
+ // S3 object tags for billing and automation
125
+ await docRef.setTags([
126
+ { Key: 'SecurityLevel', Value: 'Confidential' },
127
+ { Key: 'Department', Value: 'Legal' }
128
+ ]);
129
+
130
+ // Apply an immutable AWS S3 Legal Hold
131
+ await docRef.setLegalHold('ON');
132
+ ```
package/debug-fetch.js ADDED
@@ -0,0 +1,28 @@
1
+ import fetch from 'node-fetch';
2
+
3
+ async function testUploadUrl() {
4
+ try {
5
+ const res = await fetch('http://localhost:8787/files/upload-url', {
6
+ method: 'POST',
7
+ headers: {
8
+ 'Content-Type': 'application/json',
9
+ 'X-Tenant-ID': 'test-tenant',
10
+ 'Authorization': 'Bearer DEV_TEST_TOKEN'
11
+ },
12
+ body: JSON.stringify({
13
+ path: 'test.txt',
14
+ name: 'test.txt',
15
+ size: 1024,
16
+ contentType: 'text/plain',
17
+ userId: 'user-sdk-test'
18
+ })
19
+ });
20
+
21
+ const text = await res.text();
22
+ console.log(`Status: ${res.status}`);
23
+ console.log(`Body: ${text}`);
24
+ } catch (err) {
25
+ console.error('Fetch error:', err);
26
+ }
27
+ }
28
+ testUploadUrl();
@@ -0,0 +1,329 @@
1
+ declare class HttpClient {
2
+ private baseUrl;
3
+ private tenantId;
4
+ private headers;
5
+ constructor(config: TelestackConfig);
6
+ private _fetchWithRetry;
7
+ get<T>(path: string, params?: Record<string, string | undefined>): Promise<T>;
8
+ post<T>(path: string, body?: unknown): Promise<T>;
9
+ patch<T>(path: string, body?: unknown): Promise<T>;
10
+ put<T>(path: string, body?: unknown): Promise<T>;
11
+ delete<T>(path: string): Promise<T>;
12
+ /** Upload a file binary buffer directly to a presigned S3 URL. */
13
+ uploadToPresignedUrl(url: string, data: Blob | ArrayBuffer, contentType: string, onProgress?: (p: number) => void): Promise<void>;
14
+ private _handle;
15
+ }
16
+ declare class TelestackError extends Error {
17
+ readonly status: number;
18
+ readonly code?: string | undefined;
19
+ constructor(message: string, status: number, code?: string | undefined);
20
+ }
21
+
22
+ /**
23
+ * A cancellable, resumable, and observable upload task.
24
+ * Designed directly after Firebase Storage's UploadTask but optimized for Telestack resumable multipart pipelines.
25
+ */
26
+ declare class UploadTask implements Promise<UploadResult> {
27
+ private readonly client;
28
+ private readonly path;
29
+ private readonly name;
30
+ private readonly contentType;
31
+ private readonly options;
32
+ private _state;
33
+ private _bytesTransferred;
34
+ private _totalBytes;
35
+ private _data;
36
+ private _observers;
37
+ private _promise;
38
+ private _resolve;
39
+ private _reject;
40
+ private _uploadId?;
41
+ private _parts;
42
+ private _currentPartIndex;
43
+ private _activeXhr?;
44
+ private _isResumable;
45
+ private _simpleFileMetadata?;
46
+ constructor(client: HttpClient, path: string, data: Blob, name: string, contentType: string, options: UploadOptions);
47
+ /** Register observers for state changes, errors, and completion. */
48
+ on(event: 'state_changed', nextOrObserver?: Observer<UploadTaskSnapshot> | ((snap: UploadTaskSnapshot) => void), error?: (err: Error) => void, complete?: () => void): () => void;
49
+ /** Suspend the upload. Only works elegantly on resumable uploads, but supported for all. */
50
+ pause(): boolean;
51
+ /** Resume a paused upload. */
52
+ resume(): boolean;
53
+ /** Permanently cancel the upload, cleaning up partial server state if necessary. */
54
+ cancel(): boolean;
55
+ get snapshot(): UploadTaskSnapshot;
56
+ then<TResult1 = UploadResult, TResult2 = never>(onfulfilled?: ((value: UploadResult) => TResult1 | PromiseLike<TResult1>) | null, onrejected?: ((reason: any) => TResult2 | PromiseLike<TResult2>) | null): Promise<TResult1 | TResult2>;
57
+ catch<TResult = never>(onrejected?: ((reason: any) => TResult | PromiseLike<TResult>) | null): Promise<UploadResult | TResult>;
58
+ finally(onfinally?: (() => void) | null): Promise<UploadResult>;
59
+ readonly [Symbol.toStringTag] = "UploadTask";
60
+ private _start;
61
+ private _startSimple;
62
+ private _startResumable;
63
+ private _continueResumable;
64
+ private _uploadChunkWithProgress;
65
+ private _notifyObservers;
66
+ private _handleError;
67
+ }
68
+
69
+ interface TelestackConfig {
70
+ baseUrl: string;
71
+ apiKey?: string;
72
+ token?: string;
73
+ tenantId: string;
74
+ defaultUploadOptions?: Partial<UploadOptions>;
75
+ }
76
+ interface FileMetadata {
77
+ id: string;
78
+ tenant_id: string;
79
+ path: string;
80
+ name: string;
81
+ size: number;
82
+ content_type: string;
83
+ owner_id: string;
84
+ status: 'pending' | 'active' | 'deleted';
85
+ metadata: Record<string, any>;
86
+ created_at: string;
87
+ updated_at: string;
88
+ }
89
+ interface UploadOptions {
90
+ tenantId?: string;
91
+ userId: string;
92
+ metadata?: Record<string, any>;
93
+ chunkSize?: number;
94
+ encryptionKey?: string;
95
+ compressImage?: {
96
+ maxWidth?: number;
97
+ maxHeight?: number;
98
+ quality?: number;
99
+ };
100
+ }
101
+ interface DownloadOptions {
102
+ versionId?: string;
103
+ }
104
+ interface ListOptions {
105
+ prefix?: string;
106
+ limit?: number;
107
+ }
108
+ interface SearchOptions {
109
+ query?: string;
110
+ metadata?: Record<string, any>;
111
+ limit?: number;
112
+ }
113
+ interface SearchResult {
114
+ success: boolean;
115
+ files: FileMetadata[];
116
+ }
117
+ interface UploadResult {
118
+ file: FileMetadata;
119
+ resumable: boolean;
120
+ }
121
+ interface BatchDeleteResult {
122
+ success: boolean;
123
+ deletedCount: number;
124
+ errors: any[];
125
+ }
126
+ interface BatchCopyResult {
127
+ success: boolean;
128
+ results: any[];
129
+ }
130
+ interface TagSet {
131
+ Key: string;
132
+ Value: string;
133
+ }
134
+ /** Upload State Enum exactly like Firebase */
135
+ type UploadState = 'processing' | 'running' | 'paused' | 'success' | 'error' | 'canceled';
136
+ interface UploadTaskSnapshot {
137
+ bytesTransferred: number;
138
+ totalBytes: number;
139
+ state: UploadState;
140
+ task: UploadTask;
141
+ }
142
+ type Observer<T> = {
143
+ next?: (value: T) => void;
144
+ error?: (error: Error) => void;
145
+ complete?: () => void;
146
+ };
147
+
148
+ /**
149
+ * Base abstract reference to a location in Telestack Storage.
150
+ */
151
+ declare abstract class StorageRef {
152
+ protected readonly client: HttpClient;
153
+ readonly path: string;
154
+ readonly tenantId: string;
155
+ constructor(client: HttpClient, path: string, tenantId: string);
156
+ /** Navigate to a highly specific child path. Automatically infers File or Directory Ref. */
157
+ child(childPath: string): StorageRef;
158
+ }
159
+ /**
160
+ * Reference exclusively to a Directory (Prefix).
161
+ */
162
+ declare class DirRef extends StorageRef {
163
+ /** Retrieve all files immediately within this directory prefix. */
164
+ listAll(limit?: number): Promise<FileMetadata[]>;
165
+ }
166
+ /**
167
+ * Reference exclusively to a File object.
168
+ */
169
+ declare class FileRef extends StorageRef {
170
+ /**
171
+ * Upload a File/Blob, returning an UploadTask that can be observed, paused, and cancelled.
172
+ * Functions identically to Firebase Storage.
173
+ */
174
+ put(data: File | Blob, options: UploadOptions): UploadTask;
175
+ /**
176
+ * Upload raw bytes directly.
177
+ */
178
+ putBytes(data: ArrayBuffer, contentType: string, options: UploadOptions): UploadTask;
179
+ /** Get a time-limited presigned download URL. */
180
+ getDownloadUrl(options?: DownloadOptions): Promise<string>;
181
+ /**
182
+ * Securely downloads and decrypts an E2EE file entirely within the browser.
183
+ * Requires the exact Base64 AES-GCM key used during `put()`.
184
+ */
185
+ getDecryptedBlob(encryptionKey: string): Promise<Blob>;
186
+ /** Get the file's metadata database record. */
187
+ getMetadata(): Promise<FileMetadata>;
188
+ /** Update custom JSON metadata on the file. */
189
+ updateMetadata(metadata: Record<string, any>): Promise<FileMetadata>;
190
+ /** Permanently delete this file. */
191
+ delete(): Promise<void>;
192
+ listVersions(): Promise<{
193
+ versions: any[];
194
+ deleteMarkers: any[];
195
+ }>;
196
+ getVersionUrl(versionId: string): Promise<string>;
197
+ getTags(): Promise<TagSet[]>;
198
+ setTags(tags: TagSet[]): Promise<void>;
199
+ setLegalHold(status: 'ON' | 'OFF'): Promise<void>;
200
+ }
201
+
202
+ /**
203
+ * Fluent builder for batch file operations.
204
+ * Operations execute identically as single network requests over the backend API.
205
+ *
206
+ * @example
207
+ * await storage.batch().delete(['a.pdf', 'b.pdf']).run();
208
+ * await storage.batch().copy(['a.pdf'], 'archive/').run();
209
+ * await storage.batch().move(['a.pdf'], 'archive/').run();
210
+ */
211
+ declare class BatchBuilder {
212
+ private readonly client;
213
+ private _op;
214
+ private _paths;
215
+ private _dest;
216
+ constructor(client: HttpClient);
217
+ delete(paths: string[]): this;
218
+ copy(paths: string[], destinationPrefix: string): this;
219
+ move(paths: string[], destinationPrefix: string): this;
220
+ run(): Promise<BatchDeleteResult | BatchCopyResult>;
221
+ }
222
+
223
+ /**
224
+ * Fluent builder for searching files via Metadata or Full-Text queries.
225
+ *
226
+ * @example
227
+ * const files = await storage.query()
228
+ * .where('project', 'Q1')
229
+ * .nameContains('report')
230
+ * .limit(10)
231
+ * .get();
232
+ */
233
+ declare class QueryBuilder {
234
+ private readonly client;
235
+ private _query;
236
+ private _metadata;
237
+ private _limit;
238
+ constructor(client: HttpClient);
239
+ nameContains(text: string): this;
240
+ where(key: string, value: any): this;
241
+ limit(n: number): this;
242
+ get(): Promise<FileMetadata[]>;
243
+ }
244
+
245
+ /**
246
+ * TelestackStorage — The main SDK client.
247
+ * More comprehensive and fluent than Firebase/Appwrite Storage.
248
+ *
249
+ * @example
250
+ * ```ts
251
+ * const storage = new TelestackStorage({
252
+ * baseUrl: 'https://storage.yourdomain.com',
253
+ * tenantId: 'your_tenant_id',
254
+ * apiKey: 'tk_your_api_key',
255
+ * });
256
+ *
257
+ * // Upload with progress, pause, and resume
258
+ * const task = storage.ref('videos/demo.mp4').put(videoBlob);
259
+ * task.on('state_changed',
260
+ * (snap) => console.log((snap.bytesTransferred / snap.totalBytes) * 100 + '%'),
261
+ * (err) => console.error(err),
262
+ * () => console.log('Done!')
263
+ * );
264
+ *
265
+ * // Later...
266
+ * task.pause();
267
+ * task.resume();
268
+ * ```
269
+ */
270
+ declare class TelestackStorage {
271
+ private readonly config;
272
+ private readonly http;
273
+ readonly tenantId: string;
274
+ constructor(config: TelestackConfig);
275
+ /** Get a highly-fluent reference to a specific file. */
276
+ ref(path: string): FileRef;
277
+ /** Get a highly-fluent reference to a directory/prefix. */
278
+ dir(path: string): DirRef;
279
+ /** Helper: List files for the entire bucket/tenant or specific prefix. */
280
+ list(options?: ListOptions): Promise<FileMetadata[]>;
281
+ /** Get a fluent batch operation builder for mass-mutations over the network. */
282
+ batch(): BatchBuilder;
283
+ /** Get a fluent query builder for metadata and full-text search. */
284
+ query(): QueryBuilder;
285
+ /** Generate a new API key (admin only). The key is shown only once. */
286
+ generateApiKey(name?: string): Promise<{
287
+ apiKey: string;
288
+ message: string;
289
+ }>;
290
+ /** Revoke an API key by its ID. */
291
+ revokeApiKey(keyId: string): Promise<void>;
292
+ /** Get S3 bucket configuration details. */
293
+ getBucketInfo(): Promise<any>;
294
+ }
295
+
296
+ declare class CryptoHelper {
297
+ /**
298
+ * Generates a new random AES-GCM 256-bit encryption key.
299
+ * Returns the key as a base64 encoded string.
300
+ */
301
+ static generateKey(): Promise<string>;
302
+ /**
303
+ * Encrypts a Blob using AES-GCM.
304
+ * Prepends a random 12-byte IV to the resulting ciphertext Blob.
305
+ */
306
+ static encrypt(blob: Blob, keyBase64: string): Promise<Blob>;
307
+ /**
308
+ * Decrypts a Blob that was encrypted with `encrypt()`.
309
+ * Extracts the 12-byte IV from the front and decrypts the rest.
310
+ * Restores the original MIME type.
311
+ */
312
+ static decrypt(blob: Blob, keyBase64: string, originalMimeType?: string): Promise<Blob>;
313
+ private static _importKey;
314
+ }
315
+
316
+ declare class ImageHelper {
317
+ /**
318
+ * Compresses and resizes an image Blob using the browser's native HTML5 Canvas.
319
+ * Only processes blobs where `type` starts with 'image/'.
320
+ * If it's not an image, it returns the original blob untouched.
321
+ */
322
+ static compress(blob: Blob, options?: {
323
+ maxWidth?: number;
324
+ maxHeight?: number;
325
+ quality?: number;
326
+ }): Promise<Blob>;
327
+ }
328
+
329
+ export { BatchBuilder, type BatchCopyResult, type BatchDeleteResult, CryptoHelper, DirRef, type DownloadOptions, type FileMetadata, FileRef, HttpClient, ImageHelper, type ListOptions, type Observer, QueryBuilder, type SearchOptions, type SearchResult, StorageRef, type TagSet, type TelestackConfig, TelestackError, TelestackStorage, type UploadOptions, type UploadResult, type UploadState, UploadTask, type UploadTaskSnapshot };