@marlinjai/storage-brain-sdk 0.1.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 +190 -0
- package/dist/index.cjs +369 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +367 -0
- package/dist/index.d.ts +367 -0
- package/dist/index.js +347 -0
- package/dist/index.js.map +1 -0
- package/package.json +54 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 marlinjai
|
|
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,190 @@
|
|
|
1
|
+
# @marlinjai/storage-brain-sdk
|
|
2
|
+
|
|
3
|
+
TypeScript SDK for **Storage Brain** - an edge-native file storage service built on Cloudflare Workers, R2, and D1.
|
|
4
|
+
|
|
5
|
+
## Features
|
|
6
|
+
|
|
7
|
+
- **Simple API** - Upload, download, list, and delete files with ease
|
|
8
|
+
- **Progress tracking** - Real-time upload progress callbacks
|
|
9
|
+
- **Type-safe** - Full TypeScript support with comprehensive types
|
|
10
|
+
- **Automatic retries** - Built-in retry logic with exponential backoff
|
|
11
|
+
- **Context-aware processing** - OCR, thumbnails, and more based on file context
|
|
12
|
+
- **Zero dependencies** - Uses only native browser/Node.js APIs
|
|
13
|
+
|
|
14
|
+
## Installation
|
|
15
|
+
|
|
16
|
+
```bash
|
|
17
|
+
npm install @marlinjai/storage-brain-sdk
|
|
18
|
+
```
|
|
19
|
+
|
|
20
|
+
## Quick Start
|
|
21
|
+
|
|
22
|
+
```typescript
|
|
23
|
+
import { StorageBrain } from '@marlinjai/storage-brain-sdk';
|
|
24
|
+
|
|
25
|
+
// Initialize the client
|
|
26
|
+
const storage = new StorageBrain({
|
|
27
|
+
apiKey: 'sk_live_your_api_key_here',
|
|
28
|
+
});
|
|
29
|
+
|
|
30
|
+
// Upload a file
|
|
31
|
+
const file = await storage.upload(fileBlob, {
|
|
32
|
+
context: 'invoice', // Triggers OCR processing
|
|
33
|
+
onProgress: (progress) => console.log(`${progress}%`),
|
|
34
|
+
});
|
|
35
|
+
|
|
36
|
+
console.log('File uploaded:', file.url);
|
|
37
|
+
console.log('OCR data:', file.metadata?.ocrData);
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
## API Reference
|
|
41
|
+
|
|
42
|
+
### Constructor
|
|
43
|
+
|
|
44
|
+
```typescript
|
|
45
|
+
const storage = new StorageBrain({
|
|
46
|
+
apiKey: string; // Required: Your API key
|
|
47
|
+
baseUrl?: string; // Optional: API base URL
|
|
48
|
+
timeout?: number; // Optional: Request timeout in ms (default: 30000)
|
|
49
|
+
maxRetries?: number; // Optional: Max retry attempts (default: 3)
|
|
50
|
+
});
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
### Methods
|
|
54
|
+
|
|
55
|
+
#### `upload(file, options)`
|
|
56
|
+
|
|
57
|
+
Upload a file to Storage Brain.
|
|
58
|
+
|
|
59
|
+
```typescript
|
|
60
|
+
const result = await storage.upload(file, {
|
|
61
|
+
context: 'invoice', // Processing context
|
|
62
|
+
tags: { orderId: '123' }, // Optional metadata tags
|
|
63
|
+
onProgress: (p) => {}, // Progress callback (0-100)
|
|
64
|
+
webhookUrl: 'https://...', // Optional webhook for notifications
|
|
65
|
+
signal: abortController.signal // Optional abort signal
|
|
66
|
+
});
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
**Processing Contexts:**
|
|
70
|
+
- `invoice` - OCR text extraction via Google Cloud Vision
|
|
71
|
+
- `newsletter` - Image validation and EXIF extraction
|
|
72
|
+
- `framer-site` - Thumbnail generation (200, 400, 800px)
|
|
73
|
+
- `default` - Basic validation and storage
|
|
74
|
+
|
|
75
|
+
#### `getFile(fileId)`
|
|
76
|
+
|
|
77
|
+
Get file information by ID.
|
|
78
|
+
|
|
79
|
+
```typescript
|
|
80
|
+
const file = await storage.getFile('file-uuid');
|
|
81
|
+
```
|
|
82
|
+
|
|
83
|
+
#### `listFiles(options?)`
|
|
84
|
+
|
|
85
|
+
List files with optional filtering and pagination.
|
|
86
|
+
|
|
87
|
+
```typescript
|
|
88
|
+
const { files, nextCursor, total } = await storage.listFiles({
|
|
89
|
+
limit: 20, // Max results (default: 20)
|
|
90
|
+
cursor: 'abc...', // Pagination cursor
|
|
91
|
+
context: 'invoice', // Filter by context
|
|
92
|
+
fileType: 'image/png', // Filter by MIME type
|
|
93
|
+
});
|
|
94
|
+
```
|
|
95
|
+
|
|
96
|
+
#### `deleteFile(fileId)`
|
|
97
|
+
|
|
98
|
+
Soft delete a file.
|
|
99
|
+
|
|
100
|
+
```typescript
|
|
101
|
+
await storage.deleteFile('file-uuid');
|
|
102
|
+
```
|
|
103
|
+
|
|
104
|
+
#### `getQuota()`
|
|
105
|
+
|
|
106
|
+
Get storage quota information.
|
|
107
|
+
|
|
108
|
+
```typescript
|
|
109
|
+
const quota = await storage.getQuota();
|
|
110
|
+
console.log(`Used: ${quota.usedBytes} / ${quota.quotaBytes} bytes`);
|
|
111
|
+
console.log(`Usage: ${quota.usagePercent}%`);
|
|
112
|
+
```
|
|
113
|
+
|
|
114
|
+
#### `getTenantInfo()`
|
|
115
|
+
|
|
116
|
+
Get tenant information.
|
|
117
|
+
|
|
118
|
+
```typescript
|
|
119
|
+
const tenant = await storage.getTenantInfo();
|
|
120
|
+
console.log(`Tenant: ${tenant.name}`);
|
|
121
|
+
```
|
|
122
|
+
|
|
123
|
+
## Supported File Types
|
|
124
|
+
|
|
125
|
+
- **Images:** JPEG, PNG, WebP, GIF, AVIF
|
|
126
|
+
- **Documents:** PDF
|
|
127
|
+
|
|
128
|
+
## Error Handling
|
|
129
|
+
|
|
130
|
+
The SDK throws typed errors for different scenarios:
|
|
131
|
+
|
|
132
|
+
```typescript
|
|
133
|
+
import {
|
|
134
|
+
StorageBrainError,
|
|
135
|
+
AuthenticationError,
|
|
136
|
+
QuotaExceededError,
|
|
137
|
+
InvalidFileTypeError,
|
|
138
|
+
FileTooLargeError,
|
|
139
|
+
FileNotFoundError,
|
|
140
|
+
NetworkError,
|
|
141
|
+
UploadError,
|
|
142
|
+
} from '@marlinjai/storage-brain-sdk';
|
|
143
|
+
|
|
144
|
+
try {
|
|
145
|
+
await storage.upload(file, { context: 'invoice' });
|
|
146
|
+
} catch (error) {
|
|
147
|
+
if (error instanceof QuotaExceededError) {
|
|
148
|
+
console.error('Storage quota exceeded');
|
|
149
|
+
} else if (error instanceof InvalidFileTypeError) {
|
|
150
|
+
console.error('File type not allowed');
|
|
151
|
+
} else if (error instanceof AuthenticationError) {
|
|
152
|
+
console.error('Invalid API key');
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
```
|
|
156
|
+
|
|
157
|
+
## TypeScript Types
|
|
158
|
+
|
|
159
|
+
All types are exported for your convenience:
|
|
160
|
+
|
|
161
|
+
```typescript
|
|
162
|
+
import type {
|
|
163
|
+
StorageBrainConfig,
|
|
164
|
+
UploadOptions,
|
|
165
|
+
FileInfo,
|
|
166
|
+
ListFilesOptions,
|
|
167
|
+
ListFilesResult,
|
|
168
|
+
QuotaInfo,
|
|
169
|
+
TenantInfo,
|
|
170
|
+
FileMetadata,
|
|
171
|
+
OcrResult,
|
|
172
|
+
ProcessingContext,
|
|
173
|
+
AllowedMimeType,
|
|
174
|
+
} from '@marlinjai/storage-brain-sdk';
|
|
175
|
+
```
|
|
176
|
+
|
|
177
|
+
## Constants
|
|
178
|
+
|
|
179
|
+
```typescript
|
|
180
|
+
import {
|
|
181
|
+
ALLOWED_MIME_TYPES, // ['image/jpeg', 'image/png', ...]
|
|
182
|
+
PROCESSING_CONTEXTS, // ['newsletter', 'invoice', 'framer-site', 'default']
|
|
183
|
+
MAX_FILE_SIZE_BYTES, // 100MB
|
|
184
|
+
THUMBNAIL_SIZES, // { thumb: 200, medium: 400, large: 800 }
|
|
185
|
+
} from '@marlinjai/storage-brain-sdk';
|
|
186
|
+
```
|
|
187
|
+
|
|
188
|
+
## License
|
|
189
|
+
|
|
190
|
+
MIT
|
package/dist/index.cjs
ADDED
|
@@ -0,0 +1,369 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
Object.defineProperty(exports, '__esModule', { value: true });
|
|
4
|
+
|
|
5
|
+
// src/constants.ts
|
|
6
|
+
var ALLOWED_FILE_TYPES = {
|
|
7
|
+
// Images
|
|
8
|
+
"image/jpeg": { extension: "jpg", category: "image" },
|
|
9
|
+
"image/png": { extension: "png", category: "image" },
|
|
10
|
+
"image/webp": { extension: "webp", category: "image" },
|
|
11
|
+
"image/gif": { extension: "gif", category: "image" },
|
|
12
|
+
"image/avif": { extension: "avif", category: "image" },
|
|
13
|
+
// Documents
|
|
14
|
+
"application/pdf": { extension: "pdf", category: "document" }
|
|
15
|
+
};
|
|
16
|
+
var ALLOWED_MIME_TYPES = Object.keys(ALLOWED_FILE_TYPES);
|
|
17
|
+
var IMAGE_MIME_TYPES = ALLOWED_MIME_TYPES.filter(
|
|
18
|
+
(type) => ALLOWED_FILE_TYPES[type].category === "image"
|
|
19
|
+
);
|
|
20
|
+
var DOCUMENT_MIME_TYPES = ALLOWED_MIME_TYPES.filter(
|
|
21
|
+
(type) => ALLOWED_FILE_TYPES[type].category === "document"
|
|
22
|
+
);
|
|
23
|
+
var PROCESSING_CONTEXTS = ["newsletter", "invoice", "framer-site", "default"];
|
|
24
|
+
var MAX_FILE_SIZE_BYTES = 100 * 1024 * 1024;
|
|
25
|
+
var THUMBNAIL_SIZES = {
|
|
26
|
+
thumb: { width: 200, height: 200 },
|
|
27
|
+
medium: { width: 400, height: 400 },
|
|
28
|
+
large: { width: 800, height: 800 }
|
|
29
|
+
};
|
|
30
|
+
var PROCESSING_STATUSES = ["pending", "processing", "completed", "failed"];
|
|
31
|
+
var RETRY_CONFIG = {
|
|
32
|
+
initialDelayMs: 1e3,
|
|
33
|
+
maxDelayMs: 1e4,
|
|
34
|
+
backoffMultiplier: 2
|
|
35
|
+
};
|
|
36
|
+
|
|
37
|
+
// src/errors.ts
|
|
38
|
+
var StorageBrainError = class extends Error {
|
|
39
|
+
constructor(message, code, statusCode, details) {
|
|
40
|
+
super(message);
|
|
41
|
+
this.code = code;
|
|
42
|
+
this.statusCode = statusCode;
|
|
43
|
+
this.details = details;
|
|
44
|
+
this.name = "StorageBrainError";
|
|
45
|
+
}
|
|
46
|
+
};
|
|
47
|
+
var AuthenticationError = class extends StorageBrainError {
|
|
48
|
+
constructor(message = "Authentication failed") {
|
|
49
|
+
super(message, "AUTHENTICATION_ERROR", 401);
|
|
50
|
+
this.name = "AuthenticationError";
|
|
51
|
+
}
|
|
52
|
+
};
|
|
53
|
+
var QuotaExceededError = class extends StorageBrainError {
|
|
54
|
+
constructor(message = "Storage quota exceeded", quotaBytes, usedBytes) {
|
|
55
|
+
super(message, "QUOTA_EXCEEDED", 403, { quotaBytes, usedBytes });
|
|
56
|
+
this.quotaBytes = quotaBytes;
|
|
57
|
+
this.usedBytes = usedBytes;
|
|
58
|
+
this.name = "QuotaExceededError";
|
|
59
|
+
}
|
|
60
|
+
};
|
|
61
|
+
var InvalidFileTypeError = class extends StorageBrainError {
|
|
62
|
+
constructor(fileType, allowedTypes) {
|
|
63
|
+
super(
|
|
64
|
+
`File type '${fileType}' is not allowed`,
|
|
65
|
+
"INVALID_FILE_TYPE",
|
|
66
|
+
400,
|
|
67
|
+
{ fileType, allowedTypes }
|
|
68
|
+
);
|
|
69
|
+
this.name = "InvalidFileTypeError";
|
|
70
|
+
}
|
|
71
|
+
};
|
|
72
|
+
var FileTooLargeError = class extends StorageBrainError {
|
|
73
|
+
constructor(fileSize, maxSize) {
|
|
74
|
+
super(
|
|
75
|
+
`File size ${fileSize} bytes exceeds maximum of ${maxSize} bytes`,
|
|
76
|
+
"FILE_TOO_LARGE",
|
|
77
|
+
400,
|
|
78
|
+
{ fileSize, maxSize }
|
|
79
|
+
);
|
|
80
|
+
this.name = "FileTooLargeError";
|
|
81
|
+
}
|
|
82
|
+
};
|
|
83
|
+
var FileNotFoundError = class extends StorageBrainError {
|
|
84
|
+
constructor(fileId) {
|
|
85
|
+
super(`File not found: ${fileId}`, "FILE_NOT_FOUND", 404, { fileId });
|
|
86
|
+
this.name = "FileNotFoundError";
|
|
87
|
+
}
|
|
88
|
+
};
|
|
89
|
+
var NetworkError = class extends StorageBrainError {
|
|
90
|
+
constructor(message = "Network error occurred", originalError) {
|
|
91
|
+
super(message, "NETWORK_ERROR", void 0, { originalError: originalError?.message });
|
|
92
|
+
this.originalError = originalError;
|
|
93
|
+
this.name = "NetworkError";
|
|
94
|
+
}
|
|
95
|
+
};
|
|
96
|
+
var UploadError = class extends StorageBrainError {
|
|
97
|
+
constructor(message, originalError) {
|
|
98
|
+
super(message, "UPLOAD_ERROR", void 0, { originalError: originalError?.message });
|
|
99
|
+
this.originalError = originalError;
|
|
100
|
+
this.name = "UploadError";
|
|
101
|
+
}
|
|
102
|
+
};
|
|
103
|
+
var ValidationError = class extends StorageBrainError {
|
|
104
|
+
constructor(message, errors) {
|
|
105
|
+
super(message, "VALIDATION_ERROR", 400, { errors });
|
|
106
|
+
this.errors = errors;
|
|
107
|
+
this.name = "ValidationError";
|
|
108
|
+
}
|
|
109
|
+
};
|
|
110
|
+
function parseApiError(statusCode, response) {
|
|
111
|
+
const { code, message, details } = response.error ?? {};
|
|
112
|
+
switch (code) {
|
|
113
|
+
case "UNAUTHORIZED":
|
|
114
|
+
return new AuthenticationError(message);
|
|
115
|
+
case "QUOTA_EXCEEDED":
|
|
116
|
+
return new QuotaExceededError(
|
|
117
|
+
message,
|
|
118
|
+
details?.quotaBytes,
|
|
119
|
+
details?.usedBytes
|
|
120
|
+
);
|
|
121
|
+
case "INVALID_FILE_TYPE":
|
|
122
|
+
return new InvalidFileTypeError(
|
|
123
|
+
details?.fileType,
|
|
124
|
+
details?.allowedTypes
|
|
125
|
+
);
|
|
126
|
+
case "FILE_TOO_LARGE":
|
|
127
|
+
return new FileTooLargeError(
|
|
128
|
+
details?.fileSize,
|
|
129
|
+
details?.maxSize
|
|
130
|
+
);
|
|
131
|
+
case "FILE_NOT_FOUND":
|
|
132
|
+
case "NOT_FOUND":
|
|
133
|
+
return new FileNotFoundError(details?.fileId ?? "unknown");
|
|
134
|
+
case "VALIDATION_ERROR":
|
|
135
|
+
return new ValidationError(
|
|
136
|
+
message ?? "Validation failed",
|
|
137
|
+
details?.errors
|
|
138
|
+
);
|
|
139
|
+
default:
|
|
140
|
+
return new StorageBrainError(
|
|
141
|
+
message ?? "An error occurred",
|
|
142
|
+
code ?? "UNKNOWN_ERROR",
|
|
143
|
+
statusCode,
|
|
144
|
+
details
|
|
145
|
+
);
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
// src/client.ts
|
|
150
|
+
var DEFAULT_BASE_URL = "https://storage-brain-api.marlin-pohl.workers.dev";
|
|
151
|
+
var DEFAULT_TIMEOUT = 3e4;
|
|
152
|
+
var DEFAULT_MAX_RETRIES = 3;
|
|
153
|
+
var StorageBrain = class {
|
|
154
|
+
apiKey;
|
|
155
|
+
baseUrl;
|
|
156
|
+
timeout;
|
|
157
|
+
maxRetries;
|
|
158
|
+
constructor(config) {
|
|
159
|
+
if (!config.apiKey) {
|
|
160
|
+
throw new StorageBrainError("API key is required", "CONFIGURATION_ERROR");
|
|
161
|
+
}
|
|
162
|
+
this.apiKey = config.apiKey;
|
|
163
|
+
this.baseUrl = (config.baseUrl ?? DEFAULT_BASE_URL).replace(/\/$/, "");
|
|
164
|
+
this.timeout = config.timeout ?? DEFAULT_TIMEOUT;
|
|
165
|
+
this.maxRetries = config.maxRetries ?? DEFAULT_MAX_RETRIES;
|
|
166
|
+
}
|
|
167
|
+
/**
|
|
168
|
+
* Upload a file
|
|
169
|
+
*/
|
|
170
|
+
async upload(file, options) {
|
|
171
|
+
const { context, tags, onProgress, webhookUrl, signal } = options;
|
|
172
|
+
const fileName = file instanceof File ? file.name : "file";
|
|
173
|
+
const fileType = file.type;
|
|
174
|
+
const fileSize = file.size;
|
|
175
|
+
if (!ALLOWED_MIME_TYPES.includes(fileType)) {
|
|
176
|
+
throw new InvalidFileTypeError(fileType, [...ALLOWED_MIME_TYPES]);
|
|
177
|
+
}
|
|
178
|
+
const handshake = await this.requestUpload({
|
|
179
|
+
fileType,
|
|
180
|
+
fileName,
|
|
181
|
+
fileSizeBytes: fileSize,
|
|
182
|
+
context,
|
|
183
|
+
tags,
|
|
184
|
+
webhookUrl
|
|
185
|
+
});
|
|
186
|
+
onProgress?.(10);
|
|
187
|
+
await this.uploadToPresignedUrl(handshake.presignedUrl, file, fileType, (progress) => {
|
|
188
|
+
onProgress?.(10 + Math.round(progress * 0.8));
|
|
189
|
+
}, signal);
|
|
190
|
+
onProgress?.(90);
|
|
191
|
+
const fileInfo = await this.waitForProcessing(handshake.fileId, signal);
|
|
192
|
+
onProgress?.(100);
|
|
193
|
+
return fileInfo;
|
|
194
|
+
}
|
|
195
|
+
/**
|
|
196
|
+
* Request an upload handshake
|
|
197
|
+
*/
|
|
198
|
+
async requestUpload(params) {
|
|
199
|
+
return this.request("POST", "/api/v1/upload/request", params);
|
|
200
|
+
}
|
|
201
|
+
/**
|
|
202
|
+
* Upload file content to presigned URL
|
|
203
|
+
*/
|
|
204
|
+
async uploadToPresignedUrl(presignedUrl, file, contentType, onProgress, signal) {
|
|
205
|
+
const uploadUrl = presignedUrl.startsWith("/") ? `${this.baseUrl}${presignedUrl}` : presignedUrl;
|
|
206
|
+
try {
|
|
207
|
+
await new Promise((resolve, reject) => {
|
|
208
|
+
const xhr = new XMLHttpRequest();
|
|
209
|
+
xhr.upload.addEventListener("progress", (event) => {
|
|
210
|
+
if (event.lengthComputable && onProgress) {
|
|
211
|
+
onProgress(Math.round(event.loaded / event.total * 100));
|
|
212
|
+
}
|
|
213
|
+
});
|
|
214
|
+
xhr.addEventListener("load", () => {
|
|
215
|
+
if (xhr.status >= 200 && xhr.status < 300) {
|
|
216
|
+
resolve();
|
|
217
|
+
} else {
|
|
218
|
+
reject(new UploadError(`Upload failed with status ${xhr.status}`));
|
|
219
|
+
}
|
|
220
|
+
});
|
|
221
|
+
xhr.addEventListener("error", () => {
|
|
222
|
+
reject(new NetworkError("Network error during upload"));
|
|
223
|
+
});
|
|
224
|
+
xhr.addEventListener("abort", () => {
|
|
225
|
+
reject(new UploadError("Upload was cancelled"));
|
|
226
|
+
});
|
|
227
|
+
if (signal) {
|
|
228
|
+
signal.addEventListener("abort", () => {
|
|
229
|
+
xhr.abort();
|
|
230
|
+
});
|
|
231
|
+
}
|
|
232
|
+
xhr.open("PUT", uploadUrl);
|
|
233
|
+
xhr.setRequestHeader("Content-Type", contentType);
|
|
234
|
+
if (presignedUrl.startsWith("/")) {
|
|
235
|
+
xhr.setRequestHeader("Authorization", `Bearer ${this.apiKey}`);
|
|
236
|
+
}
|
|
237
|
+
xhr.send(file);
|
|
238
|
+
});
|
|
239
|
+
} catch (error) {
|
|
240
|
+
if (error instanceof StorageBrainError) {
|
|
241
|
+
throw error;
|
|
242
|
+
}
|
|
243
|
+
throw new UploadError(
|
|
244
|
+
"Failed to upload file",
|
|
245
|
+
error instanceof Error ? error : void 0
|
|
246
|
+
);
|
|
247
|
+
}
|
|
248
|
+
}
|
|
249
|
+
/**
|
|
250
|
+
* Wait for file processing to complete
|
|
251
|
+
*/
|
|
252
|
+
async waitForProcessing(fileId, signal, maxWaitMs = 6e4, pollIntervalMs = 1e3) {
|
|
253
|
+
const startTime = Date.now();
|
|
254
|
+
while (Date.now() - startTime < maxWaitMs) {
|
|
255
|
+
if (signal?.aborted) {
|
|
256
|
+
throw new UploadError("Operation was cancelled");
|
|
257
|
+
}
|
|
258
|
+
const file = await this.getFile(fileId);
|
|
259
|
+
if (file.processingStatus === "completed" || file.processingStatus === "failed") {
|
|
260
|
+
return file;
|
|
261
|
+
}
|
|
262
|
+
await new Promise((resolve) => setTimeout(resolve, pollIntervalMs));
|
|
263
|
+
}
|
|
264
|
+
return this.getFile(fileId);
|
|
265
|
+
}
|
|
266
|
+
/**
|
|
267
|
+
* Get a file by ID
|
|
268
|
+
*/
|
|
269
|
+
async getFile(fileId) {
|
|
270
|
+
return this.request("GET", `/api/v1/files/${fileId}`);
|
|
271
|
+
}
|
|
272
|
+
/**
|
|
273
|
+
* List files
|
|
274
|
+
*/
|
|
275
|
+
async listFiles(options) {
|
|
276
|
+
const params = new URLSearchParams();
|
|
277
|
+
if (options?.limit) params.set("limit", options.limit.toString());
|
|
278
|
+
if (options?.cursor) params.set("cursor", options.cursor);
|
|
279
|
+
if (options?.context) params.set("context", options.context);
|
|
280
|
+
if (options?.fileType) params.set("fileType", options.fileType);
|
|
281
|
+
const query = params.toString();
|
|
282
|
+
const path = query ? `/api/v1/files?${query}` : "/api/v1/files";
|
|
283
|
+
return this.request("GET", path);
|
|
284
|
+
}
|
|
285
|
+
/**
|
|
286
|
+
* Delete a file
|
|
287
|
+
*/
|
|
288
|
+
async deleteFile(fileId) {
|
|
289
|
+
await this.request("DELETE", `/api/v1/files/${fileId}`);
|
|
290
|
+
}
|
|
291
|
+
/**
|
|
292
|
+
* Get quota usage
|
|
293
|
+
*/
|
|
294
|
+
async getQuota() {
|
|
295
|
+
return this.request("GET", "/api/v1/tenant/quota");
|
|
296
|
+
}
|
|
297
|
+
/**
|
|
298
|
+
* Get tenant information
|
|
299
|
+
*/
|
|
300
|
+
async getTenantInfo() {
|
|
301
|
+
return this.request("GET", "/api/v1/tenant/info");
|
|
302
|
+
}
|
|
303
|
+
/**
|
|
304
|
+
* Make an authenticated API request with retry logic
|
|
305
|
+
*/
|
|
306
|
+
async request(method, path, body) {
|
|
307
|
+
const url = `${this.baseUrl}${path}`;
|
|
308
|
+
let lastError;
|
|
309
|
+
for (let attempt = 0; attempt < this.maxRetries; attempt++) {
|
|
310
|
+
try {
|
|
311
|
+
const controller = new AbortController();
|
|
312
|
+
const timeoutId = setTimeout(() => controller.abort(), this.timeout);
|
|
313
|
+
const response = await fetch(url, {
|
|
314
|
+
method,
|
|
315
|
+
headers: {
|
|
316
|
+
"Content-Type": "application/json",
|
|
317
|
+
Authorization: `Bearer ${this.apiKey}`
|
|
318
|
+
},
|
|
319
|
+
body: body ? JSON.stringify(body) : void 0,
|
|
320
|
+
signal: controller.signal
|
|
321
|
+
});
|
|
322
|
+
clearTimeout(timeoutId);
|
|
323
|
+
if (!response.ok) {
|
|
324
|
+
const errorBody = await response.json().catch(() => ({}));
|
|
325
|
+
throw parseApiError(response.status, errorBody);
|
|
326
|
+
}
|
|
327
|
+
return await response.json();
|
|
328
|
+
} catch (error) {
|
|
329
|
+
lastError = error instanceof Error ? error : new Error(String(error));
|
|
330
|
+
if (error instanceof StorageBrainError && error.statusCode && error.statusCode < 500) {
|
|
331
|
+
throw error;
|
|
332
|
+
}
|
|
333
|
+
if (attempt < this.maxRetries - 1) {
|
|
334
|
+
const delay = Math.min(
|
|
335
|
+
RETRY_CONFIG.initialDelayMs * Math.pow(RETRY_CONFIG.backoffMultiplier, attempt),
|
|
336
|
+
RETRY_CONFIG.maxDelayMs
|
|
337
|
+
);
|
|
338
|
+
await new Promise((resolve) => setTimeout(resolve, delay));
|
|
339
|
+
}
|
|
340
|
+
}
|
|
341
|
+
}
|
|
342
|
+
throw new NetworkError(
|
|
343
|
+
`Request failed after ${this.maxRetries} attempts`,
|
|
344
|
+
lastError
|
|
345
|
+
);
|
|
346
|
+
}
|
|
347
|
+
};
|
|
348
|
+
|
|
349
|
+
exports.ALLOWED_FILE_TYPES = ALLOWED_FILE_TYPES;
|
|
350
|
+
exports.ALLOWED_MIME_TYPES = ALLOWED_MIME_TYPES;
|
|
351
|
+
exports.AuthenticationError = AuthenticationError;
|
|
352
|
+
exports.DOCUMENT_MIME_TYPES = DOCUMENT_MIME_TYPES;
|
|
353
|
+
exports.FileNotFoundError = FileNotFoundError;
|
|
354
|
+
exports.FileTooLargeError = FileTooLargeError;
|
|
355
|
+
exports.IMAGE_MIME_TYPES = IMAGE_MIME_TYPES;
|
|
356
|
+
exports.InvalidFileTypeError = InvalidFileTypeError;
|
|
357
|
+
exports.MAX_FILE_SIZE_BYTES = MAX_FILE_SIZE_BYTES;
|
|
358
|
+
exports.NetworkError = NetworkError;
|
|
359
|
+
exports.PROCESSING_CONTEXTS = PROCESSING_CONTEXTS;
|
|
360
|
+
exports.PROCESSING_STATUSES = PROCESSING_STATUSES;
|
|
361
|
+
exports.QuotaExceededError = QuotaExceededError;
|
|
362
|
+
exports.StorageBrain = StorageBrain;
|
|
363
|
+
exports.StorageBrainError = StorageBrainError;
|
|
364
|
+
exports.THUMBNAIL_SIZES = THUMBNAIL_SIZES;
|
|
365
|
+
exports.UploadError = UploadError;
|
|
366
|
+
exports.ValidationError = ValidationError;
|
|
367
|
+
exports.default = StorageBrain;
|
|
368
|
+
//# sourceMappingURL=index.cjs.map
|
|
369
|
+
//# sourceMappingURL=index.cjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/constants.ts","../src/errors.ts","../src/client.ts"],"names":[],"mappings":";;;;;AAGO,IAAM,kBAAA,GAAqB;AAAA;AAAA,EAEhC,YAAA,EAAc,EAAE,SAAA,EAAW,KAAA,EAAO,UAAU,OAAA,EAAQ;AAAA,EACpD,WAAA,EAAa,EAAE,SAAA,EAAW,KAAA,EAAO,UAAU,OAAA,EAAQ;AAAA,EACnD,YAAA,EAAc,EAAE,SAAA,EAAW,MAAA,EAAQ,UAAU,OAAA,EAAQ;AAAA,EACrD,WAAA,EAAa,EAAE,SAAA,EAAW,KAAA,EAAO,UAAU,OAAA,EAAQ;AAAA,EACnD,YAAA,EAAc,EAAE,SAAA,EAAW,MAAA,EAAQ,UAAU,OAAA,EAAQ;AAAA;AAAA,EAErD,iBAAA,EAAmB,EAAE,SAAA,EAAW,KAAA,EAAO,UAAU,UAAA;AACnD;AAIO,IAAM,kBAAA,GAAqB,MAAA,CAAO,IAAA,CAAK,kBAAkB;AAEzD,IAAM,mBAAmB,kBAAA,CAAmB,MAAA;AAAA,EACjD,CAAC,IAAA,KAAS,kBAAA,CAAmB,IAAI,EAAE,QAAA,KAAa;AAClD;AAEO,IAAM,sBAAsB,kBAAA,CAAmB,MAAA;AAAA,EACpD,CAAC,IAAA,KAAS,kBAAA,CAAmB,IAAI,EAAE,QAAA,KAAa;AAClD;AAKO,IAAM,mBAAA,GAAsB,CAAC,YAAA,EAAc,SAAA,EAAW,eAAe,SAAS;AAM9E,IAAM,mBAAA,GAAsB,MAAM,IAAA,GAAO;AAKzC,IAAM,eAAA,GAAkB;AAAA,EAC7B,KAAA,EAAO,EAAE,KAAA,EAAO,GAAA,EAAK,QAAQ,GAAA,EAAI;AAAA,EACjC,MAAA,EAAQ,EAAE,KAAA,EAAO,GAAA,EAAK,QAAQ,GAAA,EAAI;AAAA,EAClC,KAAA,EAAO,EAAE,KAAA,EAAO,GAAA,EAAK,QAAQ,GAAA;AAC/B;AAOO,IAAM,mBAAA,GAAsB,CAAC,SAAA,EAAW,YAAA,EAAc,aAAa,QAAQ;AAM3E,IAAM,YAAA,GAAe;AAAA,EAE1B,cAAA,EAAgB,GAAA;AAAA,EAChB,UAAA,EAAY,GAAA;AAAA,EACZ,iBAAA,EAAmB;AACrB,CAAA;;;AC3DO,IAAM,iBAAA,GAAN,cAAgC,KAAA,CAAM;AAAA,EAC3C,WAAA,CACE,OAAA,EACO,IAAA,EACA,UAAA,EACA,OAAA,EACP;AACA,IAAA,KAAA,CAAM,OAAO,CAAA;AAJN,IAAA,IAAA,CAAA,IAAA,GAAA,IAAA;AACA,IAAA,IAAA,CAAA,UAAA,GAAA,UAAA;AACA,IAAA,IAAA,CAAA,OAAA,GAAA,OAAA;AAGP,IAAA,IAAA,CAAK,IAAA,GAAO,mBAAA;AAAA,EACd;AACF;AAKO,IAAM,mBAAA,GAAN,cAAkC,iBAAA,CAAkB;AAAA,EACzD,WAAA,CAAY,UAAU,uBAAA,EAAyB;AAC7C,IAAA,KAAA,CAAM,OAAA,EAAS,wBAAwB,GAAG,CAAA;AAC1C,IAAA,IAAA,CAAK,IAAA,GAAO,qBAAA;AAAA,EACd;AACF;AAKO,IAAM,kBAAA,GAAN,cAAiC,iBAAA,CAAkB;AAAA,EACxD,WAAA,CACE,OAAA,GAAU,wBAAA,EACH,UAAA,EACA,SAAA,EACP;AACA,IAAA,KAAA,CAAM,SAAS,gBAAA,EAAkB,GAAA,EAAK,EAAE,UAAA,EAAY,WAAW,CAAA;AAHxD,IAAA,IAAA,CAAA,UAAA,GAAA,UAAA;AACA,IAAA,IAAA,CAAA,SAAA,GAAA,SAAA;AAGP,IAAA,IAAA,CAAK,IAAA,GAAO,oBAAA;AAAA,EACd;AACF;AAKO,IAAM,oBAAA,GAAN,cAAmC,iBAAA,CAAkB;AAAA,EAC1D,WAAA,CACE,UACA,YAAA,EACA;AACA,IAAA,KAAA;AAAA,MACE,cAAc,QAAQ,CAAA,gBAAA,CAAA;AAAA,MACtB,mBAAA;AAAA,MACA,GAAA;AAAA,MACA,EAAE,UAAU,YAAA;AAAa,KAC3B;AACA,IAAA,IAAA,CAAK,IAAA,GAAO,sBAAA;AAAA,EACd;AACF;AAKO,IAAM,iBAAA,GAAN,cAAgC,iBAAA,CAAkB;AAAA,EACvD,WAAA,CACE,UACA,OAAA,EACA;AACA,IAAA,KAAA;AAAA,MACE,CAAA,UAAA,EAAa,QAAQ,CAAA,0BAAA,EAA6B,OAAO,CAAA,MAAA,CAAA;AAAA,MACzD,gBAAA;AAAA,MACA,GAAA;AAAA,MACA,EAAE,UAAU,OAAA;AAAQ,KACtB;AACA,IAAA,IAAA,CAAK,IAAA,GAAO,mBAAA;AAAA,EACd;AACF;AAKO,IAAM,iBAAA,GAAN,cAAgC,iBAAA,CAAkB;AAAA,EACvD,YAAY,MAAA,EAAgB;AAC1B,IAAA,KAAA,CAAM,mBAAmB,MAAM,CAAA,CAAA,EAAI,kBAAkB,GAAA,EAAK,EAAE,QAAQ,CAAA;AACpE,IAAA,IAAA,CAAK,IAAA,GAAO,mBAAA;AAAA,EACd;AACF;AAKO,IAAM,YAAA,GAAN,cAA2B,iBAAA,CAAkB;AAAA,EAClD,WAAA,CAAY,OAAA,GAAU,wBAAA,EAAiC,aAAA,EAAuB;AAC5E,IAAA,KAAA,CAAM,SAAS,eAAA,EAAiB,MAAA,EAAW,EAAE,aAAA,EAAe,aAAA,EAAe,SAAS,CAAA;AAD/B,IAAA,IAAA,CAAA,aAAA,GAAA,aAAA;AAErD,IAAA,IAAA,CAAK,IAAA,GAAO,cAAA;AAAA,EACd;AACF;AAKO,IAAM,WAAA,GAAN,cAA0B,iBAAA,CAAkB;AAAA,EACjD,WAAA,CAAY,SAAwB,aAAA,EAAuB;AACzD,IAAA,KAAA,CAAM,SAAS,cAAA,EAAgB,MAAA,EAAW,EAAE,aAAA,EAAe,aAAA,EAAe,SAAS,CAAA;AADjD,IAAA,IAAA,CAAA,aAAA,GAAA,aAAA;AAElC,IAAA,IAAA,CAAK,IAAA,GAAO,aAAA;AAAA,EACd;AACF;AAKO,IAAM,eAAA,GAAN,cAA8B,iBAAA,CAAkB;AAAA,EACrD,WAAA,CACE,SACO,MAAA,EACP;AACA,IAAA,KAAA,CAAM,OAAA,EAAS,kBAAA,EAAoB,GAAA,EAAK,EAAE,QAAQ,CAAA;AAF3C,IAAA,IAAA,CAAA,MAAA,GAAA,MAAA;AAGP,IAAA,IAAA,CAAK,IAAA,GAAO,iBAAA;AAAA,EACd;AACF;AAKO,SAAS,aAAA,CACd,YACA,QAAA,EACmB;AACnB,EAAA,MAAM,EAAE,IAAA,EAAM,OAAA,EAAS,SAAQ,GAAI,QAAA,CAAS,SAAS,EAAC;AAEtD,EAAA,QAAQ,IAAA;AAAM,IACZ,KAAK,cAAA;AACH,MAAA,OAAO,IAAI,oBAAoB,OAAO,CAAA;AAAA,IACxC,KAAK,gBAAA;AACH,MAAA,OAAO,IAAI,kBAAA;AAAA,QACT,OAAA;AAAA,QACA,OAAA,EAAS,UAAA;AAAA,QACT,OAAA,EAAS;AAAA,OACX;AAAA,IACF,KAAK,mBAAA;AACH,MAAA,OAAO,IAAI,oBAAA;AAAA,QACT,OAAA,EAAS,QAAA;AAAA,QACT,OAAA,EAAS;AAAA,OACX;AAAA,IACF,KAAK,gBAAA;AACH,MAAA,OAAO,IAAI,iBAAA;AAAA,QACT,OAAA,EAAS,QAAA;AAAA,QACT,OAAA,EAAS;AAAA,OACX;AAAA,IACF,KAAK,gBAAA;AAAA,IACL,KAAK,WAAA;AACH,MAAA,OAAO,IAAI,iBAAA,CAAkB,OAAA,EAAS,MAAA,IAAoB,SAAS,CAAA;AAAA,IACrE,KAAK,kBAAA;AACH,MAAA,OAAO,IAAI,eAAA;AAAA,QACT,OAAA,IAAW,mBAAA;AAAA,QACX,OAAA,EAAS;AAAA,OACX;AAAA,IACF;AACE,MAAA,OAAO,IAAI,iBAAA;AAAA,QACT,OAAA,IAAW,mBAAA;AAAA,QACX,IAAA,IAAQ,eAAA;AAAA,QACR,UAAA;AAAA,QACA;AAAA,OACF;AAAA;AAEN;;;AC9IA,IAAM,gBAAA,GAAmB,mDAAA;AACzB,IAAM,eAAA,GAAkB,GAAA;AACxB,IAAM,mBAAA,GAAsB,CAAA;AAoBrB,IAAM,eAAN,MAAmB;AAAA,EACP,MAAA;AAAA,EACA,OAAA;AAAA,EACA,OAAA;AAAA,EACA,UAAA;AAAA,EAEjB,YAAY,MAAA,EAA4B;AACtC,IAAA,IAAI,CAAC,OAAO,MAAA,EAAQ;AAClB,MAAA,MAAM,IAAI,iBAAA,CAAkB,qBAAA,EAAuB,qBAAqB,CAAA;AAAA,IAC1E;AAEA,IAAA,IAAA,CAAK,SAAS,MAAA,CAAO,MAAA;AACrB,IAAA,IAAA,CAAK,WAAW,MAAA,CAAO,OAAA,IAAW,gBAAA,EAAkB,OAAA,CAAQ,OAAO,EAAE,CAAA;AACrE,IAAA,IAAA,CAAK,OAAA,GAAU,OAAO,OAAA,IAAW,eAAA;AACjC,IAAA,IAAA,CAAK,UAAA,GAAa,OAAO,UAAA,IAAc,mBAAA;AAAA,EACzC;AAAA;AAAA;AAAA;AAAA,EAKA,MAAM,MAAA,CAAO,IAAA,EAAmB,OAAA,EAA2C;AACzE,IAAA,MAAM,EAAE,OAAA,EAAS,IAAA,EAAM,UAAA,EAAY,UAAA,EAAY,QAAO,GAAI,OAAA;AAG1D,IAAA,MAAM,QAAA,GAAW,IAAA,YAAgB,IAAA,GAAO,IAAA,CAAK,IAAA,GAAO,MAAA;AACpD,IAAA,MAAM,WAAW,IAAA,CAAK,IAAA;AACtB,IAAA,MAAM,WAAW,IAAA,CAAK,IAAA;AAGtB,IAAA,IAAI,CAAC,kBAAA,CAAmB,QAAA,CAAS,QAAQ,CAAA,EAAG;AAC1C,MAAA,MAAM,IAAI,oBAAA,CAAqB,QAAA,EAAU,CAAC,GAAG,kBAAkB,CAAC,CAAA;AAAA,IAClE;AAGA,IAAA,MAAM,SAAA,GAAY,MAAM,IAAA,CAAK,aAAA,CAAc;AAAA,MACzC,QAAA;AAAA,MACA,QAAA;AAAA,MACA,aAAA,EAAe,QAAA;AAAA,MACf,OAAA;AAAA,MACA,IAAA;AAAA,MACA;AAAA,KACD,CAAA;AAED,IAAA,UAAA,GAAa,EAAE,CAAA;AAGf,IAAA,MAAM,KAAK,oBAAA,CAAqB,SAAA,CAAU,cAAc,IAAA,EAAM,QAAA,EAAU,CAAC,QAAA,KAAa;AAEpF,MAAA,UAAA,GAAa,EAAA,GAAK,IAAA,CAAK,KAAA,CAAM,QAAA,GAAW,GAAG,CAAC,CAAA;AAAA,IAC9C,GAAG,MAAM,CAAA;AAET,IAAA,UAAA,GAAa,EAAE,CAAA;AAGf,IAAA,MAAM,WAAW,MAAM,IAAA,CAAK,iBAAA,CAAkB,SAAA,CAAU,QAAQ,MAAM,CAAA;AAEtE,IAAA,UAAA,GAAa,GAAG,CAAA;AAEhB,IAAA,OAAO,QAAA;AAAA,EACT;AAAA;AAAA;AAAA;AAAA,EAKA,MAAc,cAAc,MAAA,EAOC;AAC3B,IAAA,OAAO,IAAA,CAAK,OAAA,CAAyB,MAAA,EAAQ,wBAAA,EAA0B,MAAM,CAAA;AAAA,EAC/E;AAAA;AAAA;AAAA;AAAA,EAKA,MAAc,oBAAA,CACZ,YAAA,EACA,IAAA,EACA,WAAA,EACA,YACA,MAAA,EACe;AAEf,IAAA,MAAM,SAAA,GAAY,YAAA,CAAa,UAAA,CAAW,GAAG,CAAA,GACzC,GAAG,IAAA,CAAK,OAAO,CAAA,EAAG,YAAY,CAAA,CAAA,GAC9B,YAAA;AAEJ,IAAA,IAAI;AAEF,MAAA,MAAM,IAAI,OAAA,CAAc,CAAC,OAAA,EAAS,MAAA,KAAW;AAC3C,QAAA,MAAM,GAAA,GAAM,IAAI,cAAA,EAAe;AAE/B,QAAA,GAAA,CAAI,MAAA,CAAO,gBAAA,CAAiB,UAAA,EAAY,CAAC,KAAA,KAAU;AACjD,UAAA,IAAI,KAAA,CAAM,oBAAoB,UAAA,EAAY;AACxC,YAAA,UAAA,CAAW,KAAK,KAAA,CAAO,KAAA,CAAM,SAAS,KAAA,CAAM,KAAA,GAAS,GAAG,CAAC,CAAA;AAAA,UAC3D;AAAA,QACF,CAAC,CAAA;AAED,QAAA,GAAA,CAAI,gBAAA,CAAiB,QAAQ,MAAM;AACjC,UAAA,IAAI,GAAA,CAAI,MAAA,IAAU,GAAA,IAAO,GAAA,CAAI,SAAS,GAAA,EAAK;AACzC,YAAA,OAAA,EAAQ;AAAA,UACV,CAAA,MAAO;AACL,YAAA,MAAA,CAAO,IAAI,WAAA,CAAY,CAAA,0BAAA,EAA6B,GAAA,CAAI,MAAM,EAAE,CAAC,CAAA;AAAA,UACnE;AAAA,QACF,CAAC,CAAA;AAED,QAAA,GAAA,CAAI,gBAAA,CAAiB,SAAS,MAAM;AAClC,UAAA,MAAA,CAAO,IAAI,YAAA,CAAa,6BAA6B,CAAC,CAAA;AAAA,QACxD,CAAC,CAAA;AAED,QAAA,GAAA,CAAI,gBAAA,CAAiB,SAAS,MAAM;AAClC,UAAA,MAAA,CAAO,IAAI,WAAA,CAAY,sBAAsB,CAAC,CAAA;AAAA,QAChD,CAAC,CAAA;AAED,QAAA,IAAI,MAAA,EAAQ;AACV,UAAA,MAAA,CAAO,gBAAA,CAAiB,SAAS,MAAM;AACrC,YAAA,GAAA,CAAI,KAAA,EAAM;AAAA,UACZ,CAAC,CAAA;AAAA,QACH;AAEA,QAAA,GAAA,CAAI,IAAA,CAAK,OAAO,SAAS,CAAA;AACzB,QAAA,GAAA,CAAI,gBAAA,CAAiB,gBAAgB,WAAW,CAAA;AAGhD,QAAA,IAAI,YAAA,CAAa,UAAA,CAAW,GAAG,CAAA,EAAG;AAChC,UAAA,GAAA,CAAI,gBAAA,CAAiB,eAAA,EAAiB,CAAA,OAAA,EAAU,IAAA,CAAK,MAAM,CAAA,CAAE,CAAA;AAAA,QAC/D;AAEA,QAAA,GAAA,CAAI,KAAK,IAAI,CAAA;AAAA,MACf,CAAC,CAAA;AAAA,IACH,SAAS,KAAA,EAAO;AACd,MAAA,IAAI,iBAAiB,iBAAA,EAAmB;AACtC,QAAA,MAAM,KAAA;AAAA,MACR;AACA,MAAA,MAAM,IAAI,WAAA;AAAA,QACR,uBAAA;AAAA,QACA,KAAA,YAAiB,QAAQ,KAAA,GAAQ;AAAA,OACnC;AAAA,IACF;AAAA,EACF;AAAA;AAAA;AAAA;AAAA,EAKA,MAAc,iBAAA,CACZ,MAAA,EACA,QACA,SAAA,GAAY,GAAA,EACZ,iBAAiB,GAAA,EACE;AACnB,IAAA,MAAM,SAAA,GAAY,KAAK,GAAA,EAAI;AAE3B,IAAA,OAAO,IAAA,CAAK,GAAA,EAAI,GAAI,SAAA,GAAY,SAAA,EAAW;AACzC,MAAA,IAAI,QAAQ,OAAA,EAAS;AACnB,QAAA,MAAM,IAAI,YAAY,yBAAyB,CAAA;AAAA,MACjD;AAEA,MAAA,MAAM,IAAA,GAAO,MAAM,IAAA,CAAK,OAAA,CAAQ,MAAM,CAAA;AAEtC,MAAA,IAAI,IAAA,CAAK,gBAAA,KAAqB,WAAA,IAAe,IAAA,CAAK,qBAAqB,QAAA,EAAU;AAC/E,QAAA,OAAO,IAAA;AAAA,MACT;AAGA,MAAA,MAAM,IAAI,OAAA,CAAQ,CAAC,YAAY,UAAA,CAAW,OAAA,EAAS,cAAc,CAAC,CAAA;AAAA,IACpE;AAGA,IAAA,OAAO,IAAA,CAAK,QAAQ,MAAM,CAAA;AAAA,EAC5B;AAAA;AAAA;AAAA;AAAA,EAKA,MAAM,QAAQ,MAAA,EAAmC;AAC/C,IAAA,OAAO,IAAA,CAAK,OAAA,CAAkB,KAAA,EAAO,CAAA,cAAA,EAAiB,MAAM,CAAA,CAAE,CAAA;AAAA,EAChE;AAAA;AAAA;AAAA;AAAA,EAKA,MAAM,UAAU,OAAA,EAAsD;AACpE,IAAA,MAAM,MAAA,GAAS,IAAI,eAAA,EAAgB;AAEnC,IAAA,IAAI,OAAA,EAAS,OAAO,MAAA,CAAO,GAAA,CAAI,SAAS,OAAA,CAAQ,KAAA,CAAM,UAAU,CAAA;AAChE,IAAA,IAAI,SAAS,MAAA,EAAQ,MAAA,CAAO,GAAA,CAAI,QAAA,EAAU,QAAQ,MAAM,CAAA;AACxD,IAAA,IAAI,SAAS,OAAA,EAAS,MAAA,CAAO,GAAA,CAAI,SAAA,EAAW,QAAQ,OAAO,CAAA;AAC3D,IAAA,IAAI,SAAS,QAAA,EAAU,MAAA,CAAO,GAAA,CAAI,UAAA,EAAY,QAAQ,QAAQ,CAAA;AAE9D,IAAA,MAAM,KAAA,GAAQ,OAAO,QAAA,EAAS;AAC9B,IAAA,MAAM,IAAA,GAAO,KAAA,GAAQ,CAAA,cAAA,EAAiB,KAAK,CAAA,CAAA,GAAK,eAAA;AAEhD,IAAA,OAAO,IAAA,CAAK,OAAA,CAAyB,KAAA,EAAO,IAAI,CAAA;AAAA,EAClD;AAAA;AAAA;AAAA;AAAA,EAKA,MAAM,WAAW,MAAA,EAA+B;AAC9C,IAAA,MAAM,IAAA,CAAK,OAAA,CAA8B,QAAA,EAAU,CAAA,cAAA,EAAiB,MAAM,CAAA,CAAE,CAAA;AAAA,EAC9E;AAAA;AAAA;AAAA;AAAA,EAKA,MAAM,QAAA,GAA+B;AACnC,IAAA,OAAO,IAAA,CAAK,OAAA,CAAmB,KAAA,EAAO,sBAAsB,CAAA;AAAA,EAC9D;AAAA;AAAA;AAAA;AAAA,EAKA,MAAM,aAAA,GAAqC;AACzC,IAAA,OAAO,IAAA,CAAK,OAAA,CAAoB,KAAA,EAAO,qBAAqB,CAAA;AAAA,EAC9D;AAAA;AAAA;AAAA;AAAA,EAKA,MAAc,OAAA,CACZ,MAAA,EACA,IAAA,EACA,IAAA,EACY;AACZ,IAAA,MAAM,GAAA,GAAM,CAAA,EAAG,IAAA,CAAK,OAAO,GAAG,IAAI,CAAA,CAAA;AAClC,IAAA,IAAI,SAAA;AAEJ,IAAA,KAAA,IAAS,OAAA,GAAU,CAAA,EAAG,OAAA,GAAU,IAAA,CAAK,YAAY,OAAA,EAAA,EAAW;AAC1D,MAAA,IAAI;AACF,QAAA,MAAM,UAAA,GAAa,IAAI,eAAA,EAAgB;AACvC,QAAA,MAAM,YAAY,UAAA,CAAW,MAAM,WAAW,KAAA,EAAM,EAAG,KAAK,OAAO,CAAA;AAEnE,QAAA,MAAM,QAAA,GAAW,MAAM,KAAA,CAAM,GAAA,EAAK;AAAA,UAChC,MAAA;AAAA,UACA,OAAA,EAAS;AAAA,YACP,cAAA,EAAgB,kBAAA;AAAA,YAChB,aAAA,EAAe,CAAA,OAAA,EAAU,IAAA,CAAK,MAAM,CAAA;AAAA,WACtC;AAAA,UACA,IAAA,EAAM,IAAA,GAAO,IAAA,CAAK,SAAA,CAAU,IAAI,CAAA,GAAI,KAAA,CAAA;AAAA,UACpC,QAAQ,UAAA,CAAW;AAAA,SACpB,CAAA;AAED,QAAA,YAAA,CAAa,SAAS,CAAA;AAEtB,QAAA,IAAI,CAAC,SAAS,EAAA,EAAI;AAChB,UAAA,MAAM,SAAA,GAAY,MAAM,QAAA,CAAS,IAAA,GAAO,KAAA,CAAM,OAAO,EAAC,CAAE,CAAA;AACxD,UAAA,MAAM,aAAA,CAAc,QAAA,CAAS,MAAA,EAAQ,SAA+F,CAAA;AAAA,QACtI;AAEA,QAAA,OAAO,MAAM,SAAS,IAAA,EAAK;AAAA,MAC7B,SAAS,KAAA,EAAO;AACd,QAAA,SAAA,GAAY,iBAAiB,KAAA,GAAQ,KAAA,GAAQ,IAAI,KAAA,CAAM,MAAA,CAAO,KAAK,CAAC,CAAA;AAGpE,QAAA,IAAI,iBAAiB,iBAAA,IAAqB,KAAA,CAAM,UAAA,IAAc,KAAA,CAAM,aAAa,GAAA,EAAK;AACpF,UAAA,MAAM,KAAA;AAAA,QACR;AAGA,QAAA,IAAI,OAAA,GAAU,IAAA,CAAK,UAAA,GAAa,CAAA,EAAG;AACjC,UAAA,MAAM,QAAQ,IAAA,CAAK,GAAA;AAAA,YACjB,aAAa,cAAA,GAAiB,IAAA,CAAK,GAAA,CAAI,YAAA,CAAa,mBAAmB,OAAO,CAAA;AAAA,YAC9E,YAAA,CAAa;AAAA,WACf;AACA,UAAA,MAAM,IAAI,OAAA,CAAQ,CAAC,YAAY,UAAA,CAAW,OAAA,EAAS,KAAK,CAAC,CAAA;AAAA,QAC3D;AAAA,MACF;AAAA,IACF;AAEA,IAAA,MAAM,IAAI,YAAA;AAAA,MACR,CAAA,qBAAA,EAAwB,KAAK,UAAU,CAAA,SAAA,CAAA;AAAA,MACvC;AAAA,KACF;AAAA,EACF;AACF","file":"index.cjs","sourcesContent":["/**\n * Allowed MIME types for file uploads\n */\nexport const ALLOWED_FILE_TYPES = {\n // Images\n 'image/jpeg': { extension: 'jpg', category: 'image' },\n 'image/png': { extension: 'png', category: 'image' },\n 'image/webp': { extension: 'webp', category: 'image' },\n 'image/gif': { extension: 'gif', category: 'image' },\n 'image/avif': { extension: 'avif', category: 'image' },\n // Documents\n 'application/pdf': { extension: 'pdf', category: 'document' },\n} as const;\n\nexport type AllowedMimeType = keyof typeof ALLOWED_FILE_TYPES;\n\nexport const ALLOWED_MIME_TYPES = Object.keys(ALLOWED_FILE_TYPES) as AllowedMimeType[];\n\nexport const IMAGE_MIME_TYPES = ALLOWED_MIME_TYPES.filter(\n (type) => ALLOWED_FILE_TYPES[type].category === 'image'\n);\n\nexport const DOCUMENT_MIME_TYPES = ALLOWED_MIME_TYPES.filter(\n (type) => ALLOWED_FILE_TYPES[type].category === 'document'\n);\n\n/**\n * Processing contexts\n */\nexport const PROCESSING_CONTEXTS = ['newsletter', 'invoice', 'framer-site', 'default'] as const;\nexport type ProcessingContext = (typeof PROCESSING_CONTEXTS)[number];\n\n/**\n * File size limits\n */\nexport const MAX_FILE_SIZE_BYTES = 100 * 1024 * 1024; // 100MB per file\n\n/**\n * Thumbnail sizes\n */\nexport const THUMBNAIL_SIZES = {\n thumb: { width: 200, height: 200 },\n medium: { width: 400, height: 400 },\n large: { width: 800, height: 800 },\n} as const;\n\nexport type ThumbnailSize = keyof typeof THUMBNAIL_SIZES;\n\n/**\n * Processing statuses\n */\nexport const PROCESSING_STATUSES = ['pending', 'processing', 'completed', 'failed'] as const;\nexport type ProcessingStatus = (typeof PROCESSING_STATUSES)[number];\n\n/**\n * Retry configuration\n */\nexport const RETRY_CONFIG = {\n maxAttempts: 3,\n initialDelayMs: 1000,\n maxDelayMs: 10000,\n backoffMultiplier: 2,\n} as const;\n","/**\n * Base error class for Storage Brain SDK\n */\nexport class StorageBrainError extends Error {\n constructor(\n message: string,\n public code: string,\n public statusCode?: number,\n public details?: Record<string, unknown>\n ) {\n super(message);\n this.name = 'StorageBrainError';\n }\n}\n\n/**\n * Authentication error - invalid or missing API key\n */\nexport class AuthenticationError extends StorageBrainError {\n constructor(message = 'Authentication failed') {\n super(message, 'AUTHENTICATION_ERROR', 401);\n this.name = 'AuthenticationError';\n }\n}\n\n/**\n * Quota exceeded error\n */\nexport class QuotaExceededError extends StorageBrainError {\n constructor(\n message = 'Storage quota exceeded',\n public quotaBytes?: number,\n public usedBytes?: number\n ) {\n super(message, 'QUOTA_EXCEEDED', 403, { quotaBytes, usedBytes });\n this.name = 'QuotaExceededError';\n }\n}\n\n/**\n * Invalid file type error\n */\nexport class InvalidFileTypeError extends StorageBrainError {\n constructor(\n fileType: string,\n allowedTypes?: string[]\n ) {\n super(\n `File type '${fileType}' is not allowed`,\n 'INVALID_FILE_TYPE',\n 400,\n { fileType, allowedTypes }\n );\n this.name = 'InvalidFileTypeError';\n }\n}\n\n/**\n * File too large error\n */\nexport class FileTooLargeError extends StorageBrainError {\n constructor(\n fileSize: number,\n maxSize: number\n ) {\n super(\n `File size ${fileSize} bytes exceeds maximum of ${maxSize} bytes`,\n 'FILE_TOO_LARGE',\n 400,\n { fileSize, maxSize }\n );\n this.name = 'FileTooLargeError';\n }\n}\n\n/**\n * File not found error\n */\nexport class FileNotFoundError extends StorageBrainError {\n constructor(fileId: string) {\n super(`File not found: ${fileId}`, 'FILE_NOT_FOUND', 404, { fileId });\n this.name = 'FileNotFoundError';\n }\n}\n\n/**\n * Network error - connection issues\n */\nexport class NetworkError extends StorageBrainError {\n constructor(message = 'Network error occurred', public originalError?: Error) {\n super(message, 'NETWORK_ERROR', undefined, { originalError: originalError?.message });\n this.name = 'NetworkError';\n }\n}\n\n/**\n * Upload error - file upload failed\n */\nexport class UploadError extends StorageBrainError {\n constructor(message: string, public originalError?: Error) {\n super(message, 'UPLOAD_ERROR', undefined, { originalError: originalError?.message });\n this.name = 'UploadError';\n }\n}\n\n/**\n * Validation error - request validation failed\n */\nexport class ValidationError extends StorageBrainError {\n constructor(\n message: string,\n public errors?: Array<{ path: string; message: string }>\n ) {\n super(message, 'VALIDATION_ERROR', 400, { errors });\n this.name = 'ValidationError';\n }\n}\n\n/**\n * Parse API error response into appropriate error class\n */\nexport function parseApiError(\n statusCode: number,\n response: { error?: { code?: string; message?: string; details?: Record<string, unknown> } }\n): StorageBrainError {\n const { code, message, details } = response.error ?? {};\n\n switch (code) {\n case 'UNAUTHORIZED':\n return new AuthenticationError(message);\n case 'QUOTA_EXCEEDED':\n return new QuotaExceededError(\n message,\n details?.quotaBytes as number,\n details?.usedBytes as number\n );\n case 'INVALID_FILE_TYPE':\n return new InvalidFileTypeError(\n details?.fileType as string,\n details?.allowedTypes as string[]\n );\n case 'FILE_TOO_LARGE':\n return new FileTooLargeError(\n details?.fileSize as number,\n details?.maxSize as number\n );\n case 'FILE_NOT_FOUND':\n case 'NOT_FOUND':\n return new FileNotFoundError(details?.fileId as string ?? 'unknown');\n case 'VALIDATION_ERROR':\n return new ValidationError(\n message ?? 'Validation failed',\n details?.errors as Array<{ path: string; message: string }>\n );\n default:\n return new StorageBrainError(\n message ?? 'An error occurred',\n code ?? 'UNKNOWN_ERROR',\n statusCode,\n details\n );\n }\n}\n","import type { AllowedMimeType } from './constants';\nimport { ALLOWED_MIME_TYPES, RETRY_CONFIG } from './constants';\nimport type {\n StorageBrainConfig,\n UploadOptions,\n FileInfo,\n ListFilesOptions,\n ListFilesResult,\n QuotaInfo,\n TenantInfo,\n UploadHandshake,\n} from './types';\nimport {\n StorageBrainError,\n NetworkError,\n UploadError,\n InvalidFileTypeError,\n parseApiError,\n} from './errors';\n\nconst DEFAULT_BASE_URL = 'https://storage-brain-api.marlin-pohl.workers.dev';\nconst DEFAULT_TIMEOUT = 30000;\nconst DEFAULT_MAX_RETRIES = 3;\n\n/**\n * Storage Brain SDK Client\n *\n * @example\n * ```typescript\n * const storage = new StorageBrain({\n * apiKey: 'sk_live_...',\n * });\n *\n * // Upload a file\n * const file = await storage.upload(fileBlob, {\n * context: 'newsletter',\n * onProgress: (p) => console.log(`${p}%`),\n * });\n *\n * console.log(file.url);\n * ```\n */\nexport class StorageBrain {\n private readonly apiKey: string;\n private readonly baseUrl: string;\n private readonly timeout: number;\n private readonly maxRetries: number;\n\n constructor(config: StorageBrainConfig) {\n if (!config.apiKey) {\n throw new StorageBrainError('API key is required', 'CONFIGURATION_ERROR');\n }\n\n this.apiKey = config.apiKey;\n this.baseUrl = (config.baseUrl ?? DEFAULT_BASE_URL).replace(/\\/$/, '');\n this.timeout = config.timeout ?? DEFAULT_TIMEOUT;\n this.maxRetries = config.maxRetries ?? DEFAULT_MAX_RETRIES;\n }\n\n /**\n * Upload a file\n */\n async upload(file: File | Blob, options: UploadOptions): Promise<FileInfo> {\n const { context, tags, onProgress, webhookUrl, signal } = options;\n\n // Get file info\n const fileName = file instanceof File ? file.name : 'file';\n const fileType = file.type as AllowedMimeType;\n const fileSize = file.size;\n\n // Validate file type\n if (!ALLOWED_MIME_TYPES.includes(fileType)) {\n throw new InvalidFileTypeError(fileType, [...ALLOWED_MIME_TYPES]);\n }\n\n // Request upload handshake\n const handshake = await this.requestUpload({\n fileType,\n fileName,\n fileSizeBytes: fileSize,\n context,\n tags,\n webhookUrl,\n });\n\n onProgress?.(10);\n\n // Upload file to presigned URL\n await this.uploadToPresignedUrl(handshake.presignedUrl, file, fileType, (progress) => {\n // Map upload progress to 10-90%\n onProgress?.(10 + Math.round(progress * 0.8));\n }, signal);\n\n onProgress?.(90);\n\n // Poll for file status (processing may take time)\n const fileInfo = await this.waitForProcessing(handshake.fileId, signal);\n\n onProgress?.(100);\n\n return fileInfo;\n }\n\n /**\n * Request an upload handshake\n */\n private async requestUpload(params: {\n fileType: AllowedMimeType;\n fileName: string;\n fileSizeBytes: number;\n context: string;\n tags?: Record<string, string>;\n webhookUrl?: string;\n }): Promise<UploadHandshake> {\n return this.request<UploadHandshake>('POST', '/api/v1/upload/request', params);\n }\n\n /**\n * Upload file content to presigned URL\n */\n private async uploadToPresignedUrl(\n presignedUrl: string,\n file: File | Blob,\n contentType: string,\n onProgress?: (progress: number) => void,\n signal?: AbortSignal\n ): Promise<void> {\n // Determine if URL is relative (our internal endpoint) or absolute (direct R2)\n const uploadUrl = presignedUrl.startsWith('/')\n ? `${this.baseUrl}${presignedUrl}`\n : presignedUrl;\n\n try {\n // Use XMLHttpRequest for progress tracking\n await new Promise<void>((resolve, reject) => {\n const xhr = new XMLHttpRequest();\n\n xhr.upload.addEventListener('progress', (event) => {\n if (event.lengthComputable && onProgress) {\n onProgress(Math.round((event.loaded / event.total) * 100));\n }\n });\n\n xhr.addEventListener('load', () => {\n if (xhr.status >= 200 && xhr.status < 300) {\n resolve();\n } else {\n reject(new UploadError(`Upload failed with status ${xhr.status}`));\n }\n });\n\n xhr.addEventListener('error', () => {\n reject(new NetworkError('Network error during upload'));\n });\n\n xhr.addEventListener('abort', () => {\n reject(new UploadError('Upload was cancelled'));\n });\n\n if (signal) {\n signal.addEventListener('abort', () => {\n xhr.abort();\n });\n }\n\n xhr.open('PUT', uploadUrl);\n xhr.setRequestHeader('Content-Type', contentType);\n\n // Add auth header for internal endpoint\n if (presignedUrl.startsWith('/')) {\n xhr.setRequestHeader('Authorization', `Bearer ${this.apiKey}`);\n }\n\n xhr.send(file);\n });\n } catch (error) {\n if (error instanceof StorageBrainError) {\n throw error;\n }\n throw new UploadError(\n 'Failed to upload file',\n error instanceof Error ? error : undefined\n );\n }\n }\n\n /**\n * Wait for file processing to complete\n */\n private async waitForProcessing(\n fileId: string,\n signal?: AbortSignal,\n maxWaitMs = 60000,\n pollIntervalMs = 1000\n ): Promise<FileInfo> {\n const startTime = Date.now();\n\n while (Date.now() - startTime < maxWaitMs) {\n if (signal?.aborted) {\n throw new UploadError('Operation was cancelled');\n }\n\n const file = await this.getFile(fileId);\n\n if (file.processingStatus === 'completed' || file.processingStatus === 'failed') {\n return file;\n }\n\n // Wait before polling again\n await new Promise((resolve) => setTimeout(resolve, pollIntervalMs));\n }\n\n // Return current state even if not fully processed\n return this.getFile(fileId);\n }\n\n /**\n * Get a file by ID\n */\n async getFile(fileId: string): Promise<FileInfo> {\n return this.request<FileInfo>('GET', `/api/v1/files/${fileId}`);\n }\n\n /**\n * List files\n */\n async listFiles(options?: ListFilesOptions): Promise<ListFilesResult> {\n const params = new URLSearchParams();\n\n if (options?.limit) params.set('limit', options.limit.toString());\n if (options?.cursor) params.set('cursor', options.cursor);\n if (options?.context) params.set('context', options.context);\n if (options?.fileType) params.set('fileType', options.fileType);\n\n const query = params.toString();\n const path = query ? `/api/v1/files?${query}` : '/api/v1/files';\n\n return this.request<ListFilesResult>('GET', path);\n }\n\n /**\n * Delete a file\n */\n async deleteFile(fileId: string): Promise<void> {\n await this.request<{ success: boolean }>('DELETE', `/api/v1/files/${fileId}`);\n }\n\n /**\n * Get quota usage\n */\n async getQuota(): Promise<QuotaInfo> {\n return this.request<QuotaInfo>('GET', '/api/v1/tenant/quota');\n }\n\n /**\n * Get tenant information\n */\n async getTenantInfo(): Promise<TenantInfo> {\n return this.request<TenantInfo>('GET', '/api/v1/tenant/info');\n }\n\n /**\n * Make an authenticated API request with retry logic\n */\n private async request<T>(\n method: string,\n path: string,\n body?: unknown\n ): Promise<T> {\n const url = `${this.baseUrl}${path}`;\n let lastError: Error | undefined;\n\n for (let attempt = 0; attempt < this.maxRetries; attempt++) {\n try {\n const controller = new AbortController();\n const timeoutId = setTimeout(() => controller.abort(), this.timeout);\n\n const response = await fetch(url, {\n method,\n headers: {\n 'Content-Type': 'application/json',\n Authorization: `Bearer ${this.apiKey}`,\n },\n body: body ? JSON.stringify(body) : undefined,\n signal: controller.signal,\n });\n\n clearTimeout(timeoutId);\n\n if (!response.ok) {\n const errorBody = await response.json().catch(() => ({}));\n throw parseApiError(response.status, errorBody as { error?: { code?: string; message?: string; details?: Record<string, unknown> } });\n }\n\n return await response.json() as T;\n } catch (error) {\n lastError = error instanceof Error ? error : new Error(String(error));\n\n // Don't retry on client errors (4xx) or specific errors\n if (error instanceof StorageBrainError && error.statusCode && error.statusCode < 500) {\n throw error;\n }\n\n // Exponential backoff for retries\n if (attempt < this.maxRetries - 1) {\n const delay = Math.min(\n RETRY_CONFIG.initialDelayMs * Math.pow(RETRY_CONFIG.backoffMultiplier, attempt),\n RETRY_CONFIG.maxDelayMs\n );\n await new Promise((resolve) => setTimeout(resolve, delay));\n }\n }\n }\n\n throw new NetworkError(\n `Request failed after ${this.maxRetries} attempts`,\n lastError\n );\n }\n}\n\n// Default export for convenience\nexport default StorageBrain;\n"]}
|