@maravilla-labs/platform 0.1.17 → 0.1.20
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/dist/index.d.ts +229 -2
- package/dist/index.js +148 -1
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
- package/src/remote-client.ts +195 -1
- package/src/types.ts +241 -1
package/dist/index.d.ts
CHANGED
|
@@ -263,9 +263,234 @@ interface DbFindOptions {
|
|
|
263
263
|
/** Sort order specification (MongoDB-style: { field: 1 } for ascending, { field: -1 } for descending) */
|
|
264
264
|
sort?: any;
|
|
265
265
|
}
|
|
266
|
+
/**
|
|
267
|
+
* Storage interface for object/file storage operations.
|
|
268
|
+
* Provides S3-compatible API for storing and retrieving files.
|
|
269
|
+
*
|
|
270
|
+
* @example
|
|
271
|
+
* ```typescript
|
|
272
|
+
* const platform = getPlatform();
|
|
273
|
+
* const storage = platform.env.STORAGE;
|
|
274
|
+
*
|
|
275
|
+
* // Generate presigned upload URL for direct browser upload
|
|
276
|
+
* const uploadUrl = await storage.generateUploadUrl(
|
|
277
|
+
* 'profile-pics/user123.jpg',
|
|
278
|
+
* 'image/jpeg',
|
|
279
|
+
* { sizeLimit: 5 * 1024 * 1024 } // 5MB limit
|
|
280
|
+
* );
|
|
281
|
+
*
|
|
282
|
+
* // Generate download URL
|
|
283
|
+
* const downloadUrl = await storage.generateDownloadUrl(
|
|
284
|
+
* 'profile-pics/user123.jpg',
|
|
285
|
+
* { expiresIn: 3600 } // 1 hour
|
|
286
|
+
* );
|
|
287
|
+
*
|
|
288
|
+
* // Direct storage operations
|
|
289
|
+
* await storage.put('docs/report.pdf', pdfData, { contentType: 'application/pdf' });
|
|
290
|
+
* const file = await storage.get('docs/report.pdf');
|
|
291
|
+
* ```
|
|
292
|
+
*/
|
|
293
|
+
/**
|
|
294
|
+
* Source types accepted by Storage.putStream for streaming uploads.
|
|
295
|
+
*/
|
|
296
|
+
type StoragePutStreamSource = Uint8Array | ArrayBuffer | string | Blob | ReadableStream<Uint8Array | ArrayBuffer | string | number[]> | Iterable<Uint8Array | ArrayBuffer | string | number[]> | AsyncIterable<Uint8Array | ArrayBuffer | string | number[]>;
|
|
297
|
+
interface Storage {
|
|
298
|
+
/**
|
|
299
|
+
* Generate a presigned URL for uploading a file directly from the browser.
|
|
300
|
+
*
|
|
301
|
+
* @param key - The storage key/path for the file
|
|
302
|
+
* @param contentType - MIME type of the file
|
|
303
|
+
* @param options - Upload options
|
|
304
|
+
* @param options.sizeLimit - Maximum file size in bytes
|
|
305
|
+
* @returns Promise that resolves to upload URL details
|
|
306
|
+
*
|
|
307
|
+
* @example
|
|
308
|
+
* ```typescript
|
|
309
|
+
* const upload = await storage.generateUploadUrl(
|
|
310
|
+
* 'uploads/document.pdf',
|
|
311
|
+
* 'application/pdf',
|
|
312
|
+
* { sizeLimit: 10 * 1024 * 1024 } // 10MB
|
|
313
|
+
* );
|
|
314
|
+
*
|
|
315
|
+
* // Use the URL to upload from browser
|
|
316
|
+
* await fetch(upload.url, {
|
|
317
|
+
* method: upload.method,
|
|
318
|
+
* headers: upload.headers,
|
|
319
|
+
* body: fileData
|
|
320
|
+
* });
|
|
321
|
+
* ```
|
|
322
|
+
*/
|
|
323
|
+
generateUploadUrl(key: string, contentType: string, options?: {
|
|
324
|
+
sizeLimit?: number;
|
|
325
|
+
}): Promise<{
|
|
326
|
+
url: string;
|
|
327
|
+
method: string;
|
|
328
|
+
headers: Record<string, string>;
|
|
329
|
+
expiresIn: number;
|
|
330
|
+
}>;
|
|
331
|
+
/**
|
|
332
|
+
* Generate a presigned URL for downloading a file.
|
|
333
|
+
*
|
|
334
|
+
* @param key - The storage key/path for the file
|
|
335
|
+
* @param options - Download options
|
|
336
|
+
* @param options.expiresIn - URL expiration time in seconds (default: 3600)
|
|
337
|
+
* @returns Promise that resolves to download URL details
|
|
338
|
+
*
|
|
339
|
+
* @example
|
|
340
|
+
* ```typescript
|
|
341
|
+
* const download = await storage.generateDownloadUrl(
|
|
342
|
+
* 'uploads/document.pdf',
|
|
343
|
+
* { expiresIn: 7200 } // 2 hours
|
|
344
|
+
* );
|
|
345
|
+
*
|
|
346
|
+
* // Provide URL to client for download
|
|
347
|
+
* return { downloadUrl: download.url };
|
|
348
|
+
* ```
|
|
349
|
+
*/
|
|
350
|
+
generateDownloadUrl(key: string, options?: {
|
|
351
|
+
expiresIn?: number;
|
|
352
|
+
}): Promise<{
|
|
353
|
+
url: string;
|
|
354
|
+
method: string;
|
|
355
|
+
headers: Record<string, string>;
|
|
356
|
+
expiresIn: number;
|
|
357
|
+
}>;
|
|
358
|
+
/**
|
|
359
|
+
* Retrieve a file from storage.
|
|
360
|
+
*
|
|
361
|
+
* @param key - The storage key/path for the file
|
|
362
|
+
* @returns Promise that resolves to the file data, or null if not found
|
|
363
|
+
*
|
|
364
|
+
* @example
|
|
365
|
+
* ```typescript
|
|
366
|
+
* const imageData = await storage.get('profile-pics/user123.jpg');
|
|
367
|
+
* if (imageData) {
|
|
368
|
+
* // Process the image data
|
|
369
|
+
* return new Response(imageData, {
|
|
370
|
+
* headers: { 'Content-Type': 'image/jpeg' }
|
|
371
|
+
* });
|
|
372
|
+
* }
|
|
373
|
+
* ```
|
|
374
|
+
*/
|
|
375
|
+
get(key: string): Promise<Uint8Array | null>;
|
|
376
|
+
/**
|
|
377
|
+
* Store a file in storage.
|
|
378
|
+
*
|
|
379
|
+
* @param key - The storage key/path for the file
|
|
380
|
+
* @param data - The file data to store
|
|
381
|
+
* @param metadata - Optional metadata for the file
|
|
382
|
+
* @returns Promise that resolves when the operation completes
|
|
383
|
+
*
|
|
384
|
+
* @example
|
|
385
|
+
* ```typescript
|
|
386
|
+
* // Store an image with metadata
|
|
387
|
+
* await storage.put(
|
|
388
|
+
* 'profile-pics/user123.jpg',
|
|
389
|
+
* imageData,
|
|
390
|
+
* {
|
|
391
|
+
* contentType: 'image/jpeg',
|
|
392
|
+
* userId: 'user123',
|
|
393
|
+
* uploadedAt: new Date().toISOString()
|
|
394
|
+
* }
|
|
395
|
+
* );
|
|
396
|
+
* ```
|
|
397
|
+
*/
|
|
398
|
+
put(key: string, data: Uint8Array | string, metadata?: any): Promise<void>;
|
|
399
|
+
/**
|
|
400
|
+
* Store a file in storage using streaming/chunked data sources.
|
|
401
|
+
*
|
|
402
|
+
* Accepts a variety of source types (ReadableStream, Blob, ArrayBuffer,
|
|
403
|
+
* Uint8Array, string, or (async) iterables of chunks) and efficiently
|
|
404
|
+
* uploads them without requiring the caller to fully buffer the file
|
|
405
|
+
* in memory first when running in the native runtime. In the remote
|
|
406
|
+
* development client this currently buffers in memory as a fallback.
|
|
407
|
+
*
|
|
408
|
+
* Chunk types inside iterables can be Uint8Array, ArrayBuffer, number[]
|
|
409
|
+
* or string; strings are UTF-8 encoded.
|
|
410
|
+
*
|
|
411
|
+
* @param key - The storage key/path for the file
|
|
412
|
+
* @param source - Streaming or buffered data source
|
|
413
|
+
* @param metadata - Optional metadata for the file
|
|
414
|
+
* @returns Promise that resolves when the operation completes
|
|
415
|
+
*
|
|
416
|
+
* @example
|
|
417
|
+
* ```typescript
|
|
418
|
+
* // Upload a Blob (e.g. from a file input in a web app)
|
|
419
|
+
* await storage.putStream('videos/clip.mp4', fileBlob, { contentType: fileBlob.type });
|
|
420
|
+
*
|
|
421
|
+
* // Upload using an async generator that yields chunks
|
|
422
|
+
* async function *generate() {
|
|
423
|
+
* for await (const piece of someAsyncSource) {
|
|
424
|
+
* yield piece; // Uint8Array | string | ArrayBuffer
|
|
425
|
+
* }
|
|
426
|
+
* }
|
|
427
|
+
* await storage.putStream('logs/large.txt', generate(), { contentType: 'text/plain' });
|
|
428
|
+
* ```
|
|
429
|
+
*/
|
|
430
|
+
putStream(key: string, source: StoragePutStreamSource, metadata?: any): Promise<void>;
|
|
431
|
+
/**
|
|
432
|
+
* Delete a file from storage.
|
|
433
|
+
*
|
|
434
|
+
* @param key - The storage key/path for the file
|
|
435
|
+
* @returns Promise that resolves when the operation completes
|
|
436
|
+
*
|
|
437
|
+
* @example
|
|
438
|
+
* ```typescript
|
|
439
|
+
* await storage.delete('profile-pics/old-photo.jpg');
|
|
440
|
+
* ```
|
|
441
|
+
*/
|
|
442
|
+
delete(key: string): Promise<void>;
|
|
443
|
+
/**
|
|
444
|
+
* List files in storage with optional prefix filtering.
|
|
445
|
+
*
|
|
446
|
+
* @param options - Listing options
|
|
447
|
+
* @param options.prefix - Only return keys that start with this prefix
|
|
448
|
+
* @param options.limit - Maximum number of keys to return
|
|
449
|
+
* @returns Promise that resolves to list of file keys and metadata
|
|
450
|
+
*
|
|
451
|
+
* @example
|
|
452
|
+
* ```typescript
|
|
453
|
+
* // List all profile pictures
|
|
454
|
+
* const pics = await storage.list({ prefix: 'profile-pics/' });
|
|
455
|
+
*
|
|
456
|
+
* // List with pagination
|
|
457
|
+
* const files = await storage.list({ prefix: 'docs/', limit: 20 });
|
|
458
|
+
* ```
|
|
459
|
+
*/
|
|
460
|
+
list(options?: {
|
|
461
|
+
prefix?: string;
|
|
462
|
+
limit?: number;
|
|
463
|
+
}): Promise<Array<{
|
|
464
|
+
key: string;
|
|
465
|
+
size: number;
|
|
466
|
+
lastModified: string;
|
|
467
|
+
metadata?: any;
|
|
468
|
+
}>>;
|
|
469
|
+
/**
|
|
470
|
+
* Get metadata for a file without retrieving its contents.
|
|
471
|
+
*
|
|
472
|
+
* @param key - The storage key/path for the file
|
|
473
|
+
* @returns Promise that resolves to file metadata, or null if not found
|
|
474
|
+
*
|
|
475
|
+
* @example
|
|
476
|
+
* ```typescript
|
|
477
|
+
* const meta = await storage.getMetadata('uploads/document.pdf');
|
|
478
|
+
* if (meta) {
|
|
479
|
+
* console.log(`File size: ${meta.size} bytes`);
|
|
480
|
+
* console.log(`Last modified: ${meta.lastModified}`);
|
|
481
|
+
* }
|
|
482
|
+
* ```
|
|
483
|
+
*/
|
|
484
|
+
getMetadata(key: string): Promise<{
|
|
485
|
+
size: number;
|
|
486
|
+
contentType?: string;
|
|
487
|
+
lastModified: string;
|
|
488
|
+
metadata?: any;
|
|
489
|
+
} | null>;
|
|
490
|
+
}
|
|
266
491
|
/**
|
|
267
492
|
* Platform environment containing all available services.
|
|
268
|
-
* This is the main interface for accessing KV storage and
|
|
493
|
+
* This is the main interface for accessing KV storage, database, and object storage operations.
|
|
269
494
|
*/
|
|
270
495
|
interface PlatformEnv {
|
|
271
496
|
/**
|
|
@@ -280,6 +505,8 @@ interface PlatformEnv {
|
|
|
280
505
|
KV: Record<string, KvNamespace>;
|
|
281
506
|
/** Database interface for document operations */
|
|
282
507
|
DB: Database;
|
|
508
|
+
/** Object/file storage interface */
|
|
509
|
+
STORAGE: Storage;
|
|
283
510
|
}
|
|
284
511
|
/**
|
|
285
512
|
* Main platform interface providing access to all Maravilla runtime services.
|
|
@@ -423,4 +650,4 @@ declare function getPlatform(options?: {
|
|
|
423
650
|
*/
|
|
424
651
|
declare function clearPlatformCache(): void;
|
|
425
652
|
|
|
426
|
-
export { type Database, type DbFindOptions, type KvListResult, type KvNamespace, type Platform, type PlatformEnv, clearPlatformCache, getPlatform };
|
|
653
|
+
export { type Database, type DbFindOptions, type KvListResult, type KvNamespace, type Platform, type PlatformEnv, type Storage, type StoragePutStreamSource, clearPlatformCache, getPlatform };
|
package/dist/index.js
CHANGED
|
@@ -119,6 +119,151 @@ var RemoteDatabase = class {
|
|
|
119
119
|
return this.deleteOne(collection, filter);
|
|
120
120
|
}
|
|
121
121
|
};
|
|
122
|
+
var RemoteStorage = class _RemoteStorage {
|
|
123
|
+
constructor(baseUrl, headers) {
|
|
124
|
+
this.baseUrl = baseUrl;
|
|
125
|
+
this.headers = headers;
|
|
126
|
+
}
|
|
127
|
+
/**
|
|
128
|
+
* Internal method for making HTTP requests to the dev server.
|
|
129
|
+
* Handles error responses and authentication headers.
|
|
130
|
+
*
|
|
131
|
+
* @internal
|
|
132
|
+
*/
|
|
133
|
+
async fetch(url, options = {}) {
|
|
134
|
+
const response = await fetch(url, {
|
|
135
|
+
...options,
|
|
136
|
+
headers: { ...this.headers, ...options.headers }
|
|
137
|
+
});
|
|
138
|
+
if (!response.ok && response.status !== 404) {
|
|
139
|
+
const error = await response.text();
|
|
140
|
+
throw new Error(`Platform API error: ${response.status} - ${error}`);
|
|
141
|
+
}
|
|
142
|
+
return response;
|
|
143
|
+
}
|
|
144
|
+
async generateUploadUrl(key, contentType, options) {
|
|
145
|
+
return {
|
|
146
|
+
url: `${this.baseUrl}/api/storage/${encodeURIComponent(key)}`,
|
|
147
|
+
method: "PUT",
|
|
148
|
+
headers: {
|
|
149
|
+
"Content-Type": contentType,
|
|
150
|
+
...this.headers
|
|
151
|
+
// Include tenant headers
|
|
152
|
+
},
|
|
153
|
+
expiresIn: 3600
|
|
154
|
+
// 1 hour
|
|
155
|
+
};
|
|
156
|
+
}
|
|
157
|
+
async generateDownloadUrl(key, options) {
|
|
158
|
+
return {
|
|
159
|
+
url: `${this.baseUrl}/api/storage/${encodeURIComponent(key)}`,
|
|
160
|
+
method: "GET",
|
|
161
|
+
headers: {},
|
|
162
|
+
expiresIn: options?.expiresIn || 3600
|
|
163
|
+
// Default 1 hour
|
|
164
|
+
};
|
|
165
|
+
}
|
|
166
|
+
async get(key) {
|
|
167
|
+
const response = await this.fetch(`${this.baseUrl}/api/storage/${encodeURIComponent(key)}`);
|
|
168
|
+
if (response.status === 404) return null;
|
|
169
|
+
const arrayBuffer = await response.arrayBuffer();
|
|
170
|
+
return new Uint8Array(arrayBuffer);
|
|
171
|
+
}
|
|
172
|
+
async put(key, data, metadata) {
|
|
173
|
+
const bodyData = typeof data === "string" ? new TextEncoder().encode(data) : data;
|
|
174
|
+
await this.fetch(`${this.baseUrl}/api/storage/${encodeURIComponent(key)}`, {
|
|
175
|
+
method: "PUT",
|
|
176
|
+
headers: metadata ? { "X-Metadata": JSON.stringify(metadata) } : {},
|
|
177
|
+
// Cast because TypeScript's lib.dom.d.ts BodyInit union sometimes misses Uint8Array
|
|
178
|
+
body: bodyData
|
|
179
|
+
});
|
|
180
|
+
}
|
|
181
|
+
async putStream(key, source, metadata) {
|
|
182
|
+
if (typeof Blob !== "undefined" && source instanceof Blob) {
|
|
183
|
+
await this.fetch(`${this.baseUrl}/api/storage/${encodeURIComponent(key)}`, {
|
|
184
|
+
method: "PUT",
|
|
185
|
+
headers: {
|
|
186
|
+
...metadata ? { "X-Metadata": JSON.stringify(metadata) } : {},
|
|
187
|
+
"Content-Type": source.type || "application/octet-stream"
|
|
188
|
+
},
|
|
189
|
+
body: source
|
|
190
|
+
});
|
|
191
|
+
return;
|
|
192
|
+
}
|
|
193
|
+
if (source && typeof source.getReader === "function") {
|
|
194
|
+
await this.fetch(`${this.baseUrl}/api/storage/${encodeURIComponent(key)}`, {
|
|
195
|
+
method: "PUT",
|
|
196
|
+
headers: metadata ? { "X-Metadata": JSON.stringify(metadata) } : {},
|
|
197
|
+
body: source
|
|
198
|
+
});
|
|
199
|
+
return;
|
|
200
|
+
}
|
|
201
|
+
if (Symbol.asyncIterator in Object(source ?? {})) {
|
|
202
|
+
const asyncIt = source;
|
|
203
|
+
const stream = new ReadableStream({
|
|
204
|
+
async pull(controller) {
|
|
205
|
+
const { value, done } = await asyncIt[Symbol.asyncIterator]().next();
|
|
206
|
+
if (done) {
|
|
207
|
+
controller.close();
|
|
208
|
+
return;
|
|
209
|
+
}
|
|
210
|
+
controller.enqueue(_RemoteStorage.normalizeChunk(value));
|
|
211
|
+
}
|
|
212
|
+
});
|
|
213
|
+
await this.fetch(`${this.baseUrl}/api/storage/${encodeURIComponent(key)}`, {
|
|
214
|
+
method: "PUT",
|
|
215
|
+
headers: metadata ? { "X-Metadata": JSON.stringify(metadata) } : {},
|
|
216
|
+
body: stream
|
|
217
|
+
});
|
|
218
|
+
return;
|
|
219
|
+
}
|
|
220
|
+
if (Symbol.iterator in Object(source ?? {})) {
|
|
221
|
+
const it = source[Symbol.iterator]();
|
|
222
|
+
const stream = new ReadableStream({
|
|
223
|
+
pull(controller) {
|
|
224
|
+
const res = it.next();
|
|
225
|
+
if (res.done) {
|
|
226
|
+
controller.close();
|
|
227
|
+
return;
|
|
228
|
+
}
|
|
229
|
+
controller.enqueue(_RemoteStorage.normalizeChunk(res.value));
|
|
230
|
+
}
|
|
231
|
+
});
|
|
232
|
+
await this.fetch(`${this.baseUrl}/api/storage/${encodeURIComponent(key)}`, {
|
|
233
|
+
method: "PUT",
|
|
234
|
+
headers: metadata ? { "X-Metadata": JSON.stringify(metadata) } : {},
|
|
235
|
+
body: stream
|
|
236
|
+
});
|
|
237
|
+
return;
|
|
238
|
+
}
|
|
239
|
+
await this.put(key, _RemoteStorage.normalizeChunk(source), metadata);
|
|
240
|
+
}
|
|
241
|
+
static normalizeChunk(c) {
|
|
242
|
+
if (c == null) return new Uint8Array();
|
|
243
|
+
if (typeof c === "string") return new TextEncoder().encode(c);
|
|
244
|
+
if (c instanceof Uint8Array) return c;
|
|
245
|
+
if (c instanceof ArrayBuffer) return new Uint8Array(c);
|
|
246
|
+
if (Array.isArray(c)) return Uint8Array.from(c);
|
|
247
|
+
throw new Error("Unsupported chunk type in putStream");
|
|
248
|
+
}
|
|
249
|
+
async delete(key) {
|
|
250
|
+
await this.fetch(`${this.baseUrl}/api/storage/${encodeURIComponent(key)}`, {
|
|
251
|
+
method: "DELETE"
|
|
252
|
+
});
|
|
253
|
+
}
|
|
254
|
+
async list(options) {
|
|
255
|
+
const params = new URLSearchParams();
|
|
256
|
+
if (options?.prefix) params.set("prefix", options.prefix);
|
|
257
|
+
if (options?.limit) params.set("limit", options.limit.toString());
|
|
258
|
+
const response = await this.fetch(`${this.baseUrl}/api/storage?${params}`);
|
|
259
|
+
return response.json();
|
|
260
|
+
}
|
|
261
|
+
async getMetadata(key) {
|
|
262
|
+
const response = await this.fetch(`${this.baseUrl}/api/storage/${encodeURIComponent(key)}/metadata`);
|
|
263
|
+
if (response.status === 404) return null;
|
|
264
|
+
return response.json();
|
|
265
|
+
}
|
|
266
|
+
};
|
|
122
267
|
function createRemoteClient(baseUrl, tenant) {
|
|
123
268
|
const headers = {
|
|
124
269
|
"Content-Type": "application/json",
|
|
@@ -130,10 +275,12 @@ function createRemoteClient(baseUrl, tenant) {
|
|
|
130
275
|
}
|
|
131
276
|
});
|
|
132
277
|
const db = new RemoteDatabase(baseUrl, headers);
|
|
278
|
+
const storage = new RemoteStorage(baseUrl, headers);
|
|
133
279
|
return {
|
|
134
280
|
env: {
|
|
135
281
|
KV: kvProxy,
|
|
136
|
-
DB: db
|
|
282
|
+
DB: db,
|
|
283
|
+
STORAGE: storage
|
|
137
284
|
}
|
|
138
285
|
};
|
|
139
286
|
}
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/remote-client.ts","../src/index.ts"],"sourcesContent":["import type { KvNamespace, KvListResult, Database, DbFindOptions } from './types.js';\n\n/**\n * Remote KV namespace implementation that communicates with a development server.\n * This class provides the KV interface by making HTTP requests to the dev server.\n * \n * @internal\n */\nclass RemoteKvNamespace implements KvNamespace {\n constructor(\n private baseUrl: string,\n private namespace: string,\n private headers: Record<string, string>\n ) {}\n\n /**\n * Internal method for making HTTP requests to the dev server.\n * Handles error responses and authentication headers.\n * \n * @internal\n */\n private async fetch(url: string, options: RequestInit = {}) {\n const response = await fetch(url, {\n ...options,\n headers: { ...this.headers, ...options.headers },\n });\n\n if (!response.ok && response.status !== 404) {\n const error = await response.text();\n throw new Error(`Platform API error: ${response.status} - ${error}`);\n }\n\n return response;\n }\n\n async get(key: string): Promise<any> {\n const response = await this.fetch(`${this.baseUrl}/api/kv/${this.namespace}/${key}`);\n if (response.status === 404) return null;\n return response.json();\n }\n\n async put(key: string, value: any, options?: { expirationTtl?: number }): Promise<void> {\n const headers: Record<string, string> = {};\n if (options?.expirationTtl) {\n headers['X-TTL'] = options.expirationTtl.toString();\n }\n\n await this.fetch(`${this.baseUrl}/api/kv/${this.namespace}/${key}`, {\n method: 'PUT',\n headers,\n body: JSON.stringify(value),\n });\n }\n\n async delete(key: string): Promise<void> {\n await this.fetch(`${this.baseUrl}/api/kv/${this.namespace}/${key}`, {\n method: 'DELETE',\n });\n }\n\n async list(options?: { prefix?: string; limit?: number; cursor?: string }): Promise<KvListResult> {\n const response = await this.fetch(`${this.baseUrl}/api/kv/${this.namespace}`, {\n method: 'POST',\n body: JSON.stringify(options || {}),\n });\n const data = await response.json() as any;\n return {\n keys: data.result || [],\n list_complete: !data.result_info?.cursor,\n cursor: data.result_info?.cursor,\n };\n }\n}\n\n/**\n * Remote Database implementation that communicates with a development server.\n * This class provides the Database interface by making HTTP requests to the dev server.\n * \n * @internal\n */\nclass RemoteDatabase implements Database {\n constructor(\n private baseUrl: string,\n private headers: Record<string, string>\n ) {}\n\n /**\n * Internal method for making HTTP requests to the dev server.\n * Handles error responses and authentication headers.\n * \n * @internal\n */\n private async fetch(url: string, options: RequestInit = {}) {\n const response = await fetch(url, {\n ...options,\n headers: { ...this.headers, ...options.headers },\n });\n\n if (!response.ok && response.status !== 404) {\n const error = await response.text();\n throw new Error(`Platform API error: ${response.status} - ${error}`);\n }\n\n return response;\n }\n\n async find(collection: string, filter: any = {}, options: DbFindOptions = {}): Promise<any[]> {\n const response = await this.fetch(`${this.baseUrl}/api/db/${collection}`, {\n method: 'POST',\n body: JSON.stringify({ filter, options }),\n });\n return response.json() as Promise<any[]>;\n }\n\n async findOne(collection: string, filter: any): Promise<any | null> {\n const response = await this.fetch(`${this.baseUrl}/api/db/${collection}/findOne`, {\n method: 'POST',\n body: JSON.stringify(filter),\n });\n if (response.status === 404) return null;\n return response.json();\n }\n\n async insertOne(collection: string, document: any): Promise<string> {\n const response = await this.fetch(`${this.baseUrl}/api/db/${collection}`, {\n method: 'PUT',\n body: JSON.stringify(document),\n });\n const result = await response.json() as any;\n return result.id;\n }\n\n async updateOne(collection: string, filter: any, update: any): Promise<{ modified: number }> {\n const response = await this.fetch(`${this.baseUrl}/api/db/${collection}/update`, {\n method: 'POST',\n body: JSON.stringify({ filter, update }),\n });\n return response.json() as Promise<{ modified: number }>;\n }\n\n async deleteOne(collection: string, filter: any): Promise<{ deleted: number }> {\n const response = await this.fetch(`${this.baseUrl}/api/db/${collection}/delete`, {\n method: 'DELETE',\n body: JSON.stringify(filter),\n });\n return response.json() as Promise<{ deleted: number }>;\n }\n\n async deleteMany(collection: string, filter: any): Promise<{ deleted: number }> {\n // For now, deleteMany is the same as deleteOne in the remote client\n // This would need to be implemented properly in the dev server\n return this.deleteOne(collection, filter);\n }\n}\n\n/**\n * Create a remote platform client for development environments.\n * \n * This function creates a platform instance that communicates with a development\n * server over HTTP. It's used internally by getPlatform() when running in development\n * mode and no native platform is available.\n * \n * @param baseUrl - The base URL of the development server\n * @param tenant - The tenant identifier for multi-tenancy support\n * @returns A platform instance that proxies requests to the development server\n * \n * @internal\n * \n * @example\n * ```typescript\n * // This is typically called internally by getPlatform()\n * const platform = createRemoteClient('http://localhost:3001', 'dev-tenant');\n * \n * // The returned platform works the same as the native one\n * await platform.env.KV.cache.put('key', 'value');\n * await platform.env.DB.insertOne('users', { name: 'John' });\n * ```\n */\nexport function createRemoteClient(baseUrl: string, tenant: string) {\n const headers = {\n 'Content-Type': 'application/json',\n 'X-Tenant-Id': tenant,\n };\n\n // Create KV namespaces proxy that dynamically creates namespace instances\n const kvProxy = new Proxy({} as Record<string, KvNamespace>, {\n get(_, namespace: string) {\n return new RemoteKvNamespace(baseUrl, namespace, headers);\n }\n });\n\n const db = new RemoteDatabase(baseUrl, headers);\n\n return {\n env: {\n KV: kvProxy,\n DB: db,\n }\n };\n}","/**\n * @fileoverview Maravilla Platform SDK\n * \n * This package provides the main interface for accessing Maravilla runtime services\n * including Key-Value storage and Database operations. It automatically detects the\n * runtime environment and provides the appropriate implementation.\n * \n * ## Environment Detection\n * \n * The SDK automatically detects and adapts to different environments:\n * - **Production**: Uses native Maravilla runtime APIs\n * - **Development**: Connects to development server via HTTP\n * - **Testing**: Can be mocked or use development server\n * \n * ## Key Features\n * \n * - **KV Storage**: Cloudflare Workers KV-compatible API for key-value operations\n * - **Database**: MongoDB-style document database operations\n * - **Multi-tenancy**: Built-in tenant isolation and support\n * - **TypeScript**: Fully typed APIs with comprehensive JSDoc comments\n * - **Development-friendly**: Seamless development server integration\n * \n * @example\n * ```typescript\n * import { getPlatform } from '@maravilla/platform';\n * \n * const platform = getPlatform();\n * \n * // KV operations\n * await platform.env.KV.cache.put('user:123', { name: 'John' });\n * const user = await platform.env.KV.cache.get('user:123');\n * \n * // Database operations\n * const userId = await platform.env.DB.insertOne('users', {\n * name: 'John Doe',\n * email: 'john@example.com'\n * });\n * \n * const users = await platform.env.DB.find('users', { active: true });\n * ```\n * \n * @author Maravilla Team\n * @since 1.0.0\n */\n\nimport type { Platform } from './types.js';\nimport { createRemoteClient } from './remote-client.js';\n\nexport * from './types.js';\n\n/**\n * Global platform instance injected by Maravilla runtime or development tools.\n * This should not be accessed directly - use getPlatform() instead.\n * \n * @internal\n */\n\ndeclare global {\n var __maravilla_platform: Platform | undefined;\n var platform: Platform | undefined;\n}\n\nlet cachedPlatform: Platform | null = null;\n\n/**\n * Get the platform instance. This will:\n * 1. Check if running in Maravilla runtime (global.platform exists)\n * 2. Check if a platform was injected via vite plugin or hooks\n * 3. Fall back to remote client for development\n * \n * The platform provides access to KV storage and database operations,\n * with automatic environment detection for seamless development and production.\n * \n * @param options - Optional configuration for development mode\n * @param options.devServerUrl - URL of the development server (defaults to MARAVILLA_DEV_SERVER env var or http://localhost:3001)\n * @param options.tenant - Tenant identifier for multi-tenancy (defaults to MARAVILLA_TENANT env var or 'dev-tenant')\n * @returns Platform instance with access to KV and database services\n * \n * @example\n * ```typescript\n * import { getPlatform } from '@maravilla/platform';\n * \n * // Basic usage (auto-detects environment)\n * const platform = getPlatform();\n * \n * // Development with custom server\n * const platform = getPlatform({\n * devServerUrl: 'http://localhost:3001',\n * tenant: 'my-app'\n * });\n * \n * // Use KV storage\n * await platform.env.KV.cache.put('user:123', { name: 'John' });\n * const user = await platform.env.KV.cache.get('user:123');\n * \n * // Use database\n * const userId = await platform.env.DB.insertOne('users', {\n * name: 'John Doe',\n * email: 'john@example.com'\n * });\n * ```\n */\nexport function getPlatform(options?: {\n devServerUrl?: string;\n tenant?: string;\n}): Platform {\n // Return cached if available\n if (cachedPlatform) {\n return cachedPlatform;\n }\n\n // 1. Check if we're in the real Maravilla runtime\n if (typeof globalThis !== 'undefined') {\n // Check for native platform (in production runtime)\n if (globalThis.platform) {\n console.log('[platform] Using native Maravilla platform');\n cachedPlatform = globalThis.platform;\n return cachedPlatform;\n }\n\n // Check for injected platform (from vite plugin or hooks)\n if (globalThis.__maravilla_platform) {\n console.log('[platform] Using injected platform');\n cachedPlatform = globalThis.__maravilla_platform;\n return cachedPlatform;\n }\n }\n\n // 2. Fall back to remote client for development\n const devServerUrl = options?.devServerUrl || process.env.MARAVILLA_DEV_SERVER || 'http://localhost:3001';\n const tenant = options?.tenant || process.env.MARAVILLA_TENANT || 'dev-tenant';\n \n console.log(`[platform] Creating remote client for ${devServerUrl}`);\n cachedPlatform = createRemoteClient(devServerUrl, tenant);\n \n // Cache it globally for other modules\n if (typeof globalThis !== 'undefined') {\n globalThis.__maravilla_platform = cachedPlatform;\n }\n \n return cachedPlatform;\n}\n\n/**\n * Clear the cached platform instance (useful for testing).\n * \n * This function resets the internal platform cache and clears any globally\n * injected platform instances. Subsequent calls to getPlatform() will\n * re-initialize the platform based on the current environment.\n * \n * @example\n * ```typescript\n * import { clearPlatformCache, getPlatform } from '@maravilla/platform';\n * \n * // In tests, clear cache between test cases\n * afterEach(() => {\n * clearPlatformCache();\n * });\n * \n * // Or when switching between different environments\n * clearPlatformCache();\n * const newPlatform = getPlatform({ devServerUrl: 'http://localhost:3002' });\n * ```\n */\nexport function clearPlatformCache(): void {\n cachedPlatform = null;\n if (typeof globalThis !== 'undefined') {\n globalThis.__maravilla_platform = undefined;\n }\n}"],"mappings":";AAQA,IAAM,oBAAN,MAA+C;AAAA,EAC7C,YACU,SACA,WACA,SACR;AAHQ;AACA;AACA;AAAA,EACP;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAQH,MAAc,MAAM,KAAa,UAAuB,CAAC,GAAG;AAC1D,UAAM,WAAW,MAAM,MAAM,KAAK;AAAA,MAChC,GAAG;AAAA,MACH,SAAS,EAAE,GAAG,KAAK,SAAS,GAAG,QAAQ,QAAQ;AAAA,IACjD,CAAC;AAED,QAAI,CAAC,SAAS,MAAM,SAAS,WAAW,KAAK;AAC3C,YAAM,QAAQ,MAAM,SAAS,KAAK;AAClC,YAAM,IAAI,MAAM,uBAAuB,SAAS,MAAM,MAAM,KAAK,EAAE;AAAA,IACrE;AAEA,WAAO;AAAA,EACT;AAAA,EAEA,MAAM,IAAI,KAA2B;AACnC,UAAM,WAAW,MAAM,KAAK,MAAM,GAAG,KAAK,OAAO,WAAW,KAAK,SAAS,IAAI,GAAG,EAAE;AACnF,QAAI,SAAS,WAAW,IAAK,QAAO;AACpC,WAAO,SAAS,KAAK;AAAA,EACvB;AAAA,EAEA,MAAM,IAAI,KAAa,OAAY,SAAqD;AACtF,UAAM,UAAkC,CAAC;AACzC,QAAI,SAAS,eAAe;AAC1B,cAAQ,OAAO,IAAI,QAAQ,cAAc,SAAS;AAAA,IACpD;AAEA,UAAM,KAAK,MAAM,GAAG,KAAK,OAAO,WAAW,KAAK,SAAS,IAAI,GAAG,IAAI;AAAA,MAClE,QAAQ;AAAA,MACR;AAAA,MACA,MAAM,KAAK,UAAU,KAAK;AAAA,IAC5B,CAAC;AAAA,EACH;AAAA,EAEA,MAAM,OAAO,KAA4B;AACvC,UAAM,KAAK,MAAM,GAAG,KAAK,OAAO,WAAW,KAAK,SAAS,IAAI,GAAG,IAAI;AAAA,MAClE,QAAQ;AAAA,IACV,CAAC;AAAA,EACH;AAAA,EAEA,MAAM,KAAK,SAAuF;AAChG,UAAM,WAAW,MAAM,KAAK,MAAM,GAAG,KAAK,OAAO,WAAW,KAAK,SAAS,IAAI;AAAA,MAC5E,QAAQ;AAAA,MACR,MAAM,KAAK,UAAU,WAAW,CAAC,CAAC;AAAA,IACpC,CAAC;AACD,UAAM,OAAO,MAAM,SAAS,KAAK;AACjC,WAAO;AAAA,MACL,MAAM,KAAK,UAAU,CAAC;AAAA,MACtB,eAAe,CAAC,KAAK,aAAa;AAAA,MAClC,QAAQ,KAAK,aAAa;AAAA,IAC5B;AAAA,EACF;AACF;AAQA,IAAM,iBAAN,MAAyC;AAAA,EACvC,YACU,SACA,SACR;AAFQ;AACA;AAAA,EACP;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAQH,MAAc,MAAM,KAAa,UAAuB,CAAC,GAAG;AAC1D,UAAM,WAAW,MAAM,MAAM,KAAK;AAAA,MAChC,GAAG;AAAA,MACH,SAAS,EAAE,GAAG,KAAK,SAAS,GAAG,QAAQ,QAAQ;AAAA,IACjD,CAAC;AAED,QAAI,CAAC,SAAS,MAAM,SAAS,WAAW,KAAK;AAC3C,YAAM,QAAQ,MAAM,SAAS,KAAK;AAClC,YAAM,IAAI,MAAM,uBAAuB,SAAS,MAAM,MAAM,KAAK,EAAE;AAAA,IACrE;AAEA,WAAO;AAAA,EACT;AAAA,EAEA,MAAM,KAAK,YAAoB,SAAc,CAAC,GAAG,UAAyB,CAAC,GAAmB;AAC5F,UAAM,WAAW,MAAM,KAAK,MAAM,GAAG,KAAK,OAAO,WAAW,UAAU,IAAI;AAAA,MACxE,QAAQ;AAAA,MACR,MAAM,KAAK,UAAU,EAAE,QAAQ,QAAQ,CAAC;AAAA,IAC1C,CAAC;AACD,WAAO,SAAS,KAAK;AAAA,EACvB;AAAA,EAEA,MAAM,QAAQ,YAAoB,QAAkC;AAClE,UAAM,WAAW,MAAM,KAAK,MAAM,GAAG,KAAK,OAAO,WAAW,UAAU,YAAY;AAAA,MAChF,QAAQ;AAAA,MACR,MAAM,KAAK,UAAU,MAAM;AAAA,IAC7B,CAAC;AACD,QAAI,SAAS,WAAW,IAAK,QAAO;AACpC,WAAO,SAAS,KAAK;AAAA,EACvB;AAAA,EAEA,MAAM,UAAU,YAAoB,UAAgC;AAClE,UAAM,WAAW,MAAM,KAAK,MAAM,GAAG,KAAK,OAAO,WAAW,UAAU,IAAI;AAAA,MACxE,QAAQ;AAAA,MACR,MAAM,KAAK,UAAU,QAAQ;AAAA,IAC/B,CAAC;AACD,UAAM,SAAS,MAAM,SAAS,KAAK;AACnC,WAAO,OAAO;AAAA,EAChB;AAAA,EAEA,MAAM,UAAU,YAAoB,QAAa,QAA4C;AAC3F,UAAM,WAAW,MAAM,KAAK,MAAM,GAAG,KAAK,OAAO,WAAW,UAAU,WAAW;AAAA,MAC/E,QAAQ;AAAA,MACR,MAAM,KAAK,UAAU,EAAE,QAAQ,OAAO,CAAC;AAAA,IACzC,CAAC;AACD,WAAO,SAAS,KAAK;AAAA,EACvB;AAAA,EAEA,MAAM,UAAU,YAAoB,QAA2C;AAC7E,UAAM,WAAW,MAAM,KAAK,MAAM,GAAG,KAAK,OAAO,WAAW,UAAU,WAAW;AAAA,MAC/E,QAAQ;AAAA,MACR,MAAM,KAAK,UAAU,MAAM;AAAA,IAC7B,CAAC;AACD,WAAO,SAAS,KAAK;AAAA,EACvB;AAAA,EAEA,MAAM,WAAW,YAAoB,QAA2C;AAG9E,WAAO,KAAK,UAAU,YAAY,MAAM;AAAA,EAC1C;AACF;AAyBO,SAAS,mBAAmB,SAAiB,QAAgB;AAClE,QAAM,UAAU;AAAA,IACd,gBAAgB;AAAA,IAChB,eAAe;AAAA,EACjB;AAGA,QAAM,UAAU,IAAI,MAAM,CAAC,GAAkC;AAAA,IAC3D,IAAI,GAAG,WAAmB;AACxB,aAAO,IAAI,kBAAkB,SAAS,WAAW,OAAO;AAAA,IAC1D;AAAA,EACF,CAAC;AAED,QAAM,KAAK,IAAI,eAAe,SAAS,OAAO;AAE9C,SAAO;AAAA,IACL,KAAK;AAAA,MACH,IAAI;AAAA,MACJ,IAAI;AAAA,IACN;AAAA,EACF;AACF;;;ACzIA,IAAI,iBAAkC;AAwC/B,SAAS,YAAY,SAGf;AAEX,MAAI,gBAAgB;AAClB,WAAO;AAAA,EACT;AAGA,MAAI,OAAO,eAAe,aAAa;AAErC,QAAI,WAAW,UAAU;AACvB,cAAQ,IAAI,4CAA4C;AACxD,uBAAiB,WAAW;AAC5B,aAAO;AAAA,IACT;AAGA,QAAI,WAAW,sBAAsB;AACnC,cAAQ,IAAI,oCAAoC;AAChD,uBAAiB,WAAW;AAC5B,aAAO;AAAA,IACT;AAAA,EACF;AAGA,QAAM,eAAe,SAAS,gBAAgB,QAAQ,IAAI,wBAAwB;AAClF,QAAM,SAAS,SAAS,UAAU,QAAQ,IAAI,oBAAoB;AAElE,UAAQ,IAAI,yCAAyC,YAAY,EAAE;AACnE,mBAAiB,mBAAmB,cAAc,MAAM;AAGxD,MAAI,OAAO,eAAe,aAAa;AACrC,eAAW,uBAAuB;AAAA,EACpC;AAEA,SAAO;AACT;AAuBO,SAAS,qBAA2B;AACzC,mBAAiB;AACjB,MAAI,OAAO,eAAe,aAAa;AACrC,eAAW,uBAAuB;AAAA,EACpC;AACF;","names":[]}
|
|
1
|
+
{"version":3,"sources":["../src/remote-client.ts","../src/index.ts"],"sourcesContent":["import type { KvNamespace, KvListResult, Database, DbFindOptions, Storage } from './types.js';\n\n/**\n * Remote KV namespace implementation that communicates with a development server.\n * This class provides the KV interface by making HTTP requests to the dev server.\n * \n * @internal\n */\nclass RemoteKvNamespace implements KvNamespace {\n constructor(\n private baseUrl: string,\n private namespace: string,\n private headers: Record<string, string>\n ) {}\n\n /**\n * Internal method for making HTTP requests to the dev server.\n * Handles error responses and authentication headers.\n * \n * @internal\n */\n private async fetch(url: string, options: RequestInit = {}) {\n const response = await fetch(url, {\n ...options,\n headers: { ...this.headers, ...options.headers },\n });\n\n if (!response.ok && response.status !== 404) {\n const error = await response.text();\n throw new Error(`Platform API error: ${response.status} - ${error}`);\n }\n\n return response;\n }\n\n async get(key: string): Promise<any> {\n const response = await this.fetch(`${this.baseUrl}/api/kv/${this.namespace}/${key}`);\n if (response.status === 404) return null;\n return response.json();\n }\n\n async put(key: string, value: any, options?: { expirationTtl?: number }): Promise<void> {\n const headers: Record<string, string> = {};\n if (options?.expirationTtl) {\n headers['X-TTL'] = options.expirationTtl.toString();\n }\n\n await this.fetch(`${this.baseUrl}/api/kv/${this.namespace}/${key}`, {\n method: 'PUT',\n headers,\n body: JSON.stringify(value),\n });\n }\n\n async delete(key: string): Promise<void> {\n await this.fetch(`${this.baseUrl}/api/kv/${this.namespace}/${key}`, {\n method: 'DELETE',\n });\n }\n\n async list(options?: { prefix?: string; limit?: number; cursor?: string }): Promise<KvListResult> {\n const response = await this.fetch(`${this.baseUrl}/api/kv/${this.namespace}`, {\n method: 'POST',\n body: JSON.stringify(options || {}),\n });\n const data = await response.json() as any;\n return {\n keys: data.result || [],\n list_complete: !data.result_info?.cursor,\n cursor: data.result_info?.cursor,\n };\n }\n}\n\n/**\n * Remote Database implementation that communicates with a development server.\n * This class provides the Database interface by making HTTP requests to the dev server.\n * \n * @internal\n */\nclass RemoteDatabase implements Database {\n constructor(\n private baseUrl: string,\n private headers: Record<string, string>\n ) {}\n\n /**\n * Internal method for making HTTP requests to the dev server.\n * Handles error responses and authentication headers.\n * \n * @internal\n */\n private async fetch(url: string, options: RequestInit = {}) {\n const response = await fetch(url, {\n ...options,\n headers: { ...this.headers, ...options.headers },\n });\n\n if (!response.ok && response.status !== 404) {\n const error = await response.text();\n throw new Error(`Platform API error: ${response.status} - ${error}`);\n }\n\n return response;\n }\n\n async find(collection: string, filter: any = {}, options: DbFindOptions = {}): Promise<any[]> {\n const response = await this.fetch(`${this.baseUrl}/api/db/${collection}`, {\n method: 'POST',\n body: JSON.stringify({ filter, options }),\n });\n return response.json() as Promise<any[]>;\n }\n\n async findOne(collection: string, filter: any): Promise<any | null> {\n const response = await this.fetch(`${this.baseUrl}/api/db/${collection}/findOne`, {\n method: 'POST',\n body: JSON.stringify(filter),\n });\n if (response.status === 404) return null;\n return response.json();\n }\n\n async insertOne(collection: string, document: any): Promise<string> {\n const response = await this.fetch(`${this.baseUrl}/api/db/${collection}`, {\n method: 'PUT',\n body: JSON.stringify(document),\n });\n const result = await response.json() as any;\n return result.id;\n }\n\n async updateOne(collection: string, filter: any, update: any): Promise<{ modified: number }> {\n const response = await this.fetch(`${this.baseUrl}/api/db/${collection}/update`, {\n method: 'POST',\n body: JSON.stringify({ filter, update }),\n });\n return response.json() as Promise<{ modified: number }>;\n }\n\n async deleteOne(collection: string, filter: any): Promise<{ deleted: number }> {\n const response = await this.fetch(`${this.baseUrl}/api/db/${collection}/delete`, {\n method: 'DELETE',\n body: JSON.stringify(filter),\n });\n return response.json() as Promise<{ deleted: number }>;\n }\n\n async deleteMany(collection: string, filter: any): Promise<{ deleted: number }> {\n // For now, deleteMany is the same as deleteOne in the remote client\n // This would need to be implemented properly in the dev server\n return this.deleteOne(collection, filter);\n }\n}\n\n/**\n * Remote Storage implementation that communicates with a development server.\n * This class provides the Storage interface by making HTTP requests to the dev server.\n * \n * @internal\n */\nclass RemoteStorage implements Storage {\n constructor(\n private baseUrl: string,\n private headers: Record<string, string>\n ) {}\n\n /**\n * Internal method for making HTTP requests to the dev server.\n * Handles error responses and authentication headers.\n * \n * @internal\n */\n private async fetch(url: string, options: RequestInit = {}) {\n const response = await fetch(url, {\n ...options,\n headers: { ...this.headers, ...options.headers },\n });\n\n if (!response.ok && response.status !== 404) {\n const error = await response.text();\n throw new Error(`Platform API error: ${response.status} - ${error}`);\n }\n\n return response;\n }\n\n async generateUploadUrl(key: string, contentType: string, options?: { sizeLimit?: number }): Promise<{\n url: string;\n method: string;\n headers: Record<string, string>;\n expiresIn: number;\n }> {\n // For development, return a direct upload URL to the dev server\n // The dev server supports PUT directly to /api/storage/{key}\n return {\n url: `${this.baseUrl}/api/storage/${encodeURIComponent(key)}`,\n method: 'PUT',\n headers: {\n 'Content-Type': contentType,\n ...this.headers, // Include tenant headers\n },\n expiresIn: 3600, // 1 hour\n };\n }\n\n async generateDownloadUrl(key: string, options?: { expiresIn?: number }): Promise<{\n url: string;\n method: string;\n headers: Record<string, string>;\n expiresIn: number;\n }> {\n // For development, return a direct download URL to the dev server\n // In production, this would call the actual storage service for presigned URLs\n return {\n url: `${this.baseUrl}/api/storage/${encodeURIComponent(key)}`,\n method: 'GET',\n headers: {},\n expiresIn: options?.expiresIn || 3600, // Default 1 hour\n };\n }\n\n async get(key: string): Promise<Uint8Array | null> {\n const response = await this.fetch(`${this.baseUrl}/api/storage/${encodeURIComponent(key)}`);\n if (response.status === 404) return null;\n const arrayBuffer = await response.arrayBuffer();\n return new Uint8Array(arrayBuffer);\n }\n\n async put(key: string, data: Uint8Array | string, metadata?: any): Promise<void> {\n // Convert string to Uint8Array if needed\n const bodyData = typeof data === 'string' \n ? new TextEncoder().encode(data)\n : data;\n\n await this.fetch(`${this.baseUrl}/api/storage/${encodeURIComponent(key)}`, {\n method: 'PUT',\n headers: metadata ? { 'X-Metadata': JSON.stringify(metadata) } : {},\n // Cast because TypeScript's lib.dom.d.ts BodyInit union sometimes misses Uint8Array\n body: bodyData as any,\n });\n }\n\n async putStream(key: string, source: any, metadata?: any): Promise<void> {\n // If it's a Blob we can send directly\n if (typeof Blob !== 'undefined' && source instanceof Blob) {\n await this.fetch(`${this.baseUrl}/api/storage/${encodeURIComponent(key)}`, {\n method: 'PUT',\n headers: {\n ...(metadata ? { 'X-Metadata': JSON.stringify(metadata) } : {}),\n 'Content-Type': source.type || 'application/octet-stream'\n },\n body: source as any,\n });\n return;\n }\n\n // ReadableStream\n if (source && typeof source.getReader === 'function') {\n await this.fetch(`${this.baseUrl}/api/storage/${encodeURIComponent(key)}`, {\n method: 'PUT',\n headers: metadata ? { 'X-Metadata': JSON.stringify(metadata) } : {},\n body: source as any,\n });\n return;\n }\n\n // Async iterable -> wrap in ReadableStream\n if (Symbol.asyncIterator in Object(source ?? {})) {\n const asyncIt = source as AsyncIterable<any>;\n const stream = new ReadableStream({\n async pull(controller) {\n const { value, done } = await asyncIt[Symbol.asyncIterator]().next();\n if (done) { controller.close(); return; }\n controller.enqueue(RemoteStorage.normalizeChunk(value));\n }\n });\n await this.fetch(`${this.baseUrl}/api/storage/${encodeURIComponent(key)}`, {\n method: 'PUT',\n headers: metadata ? { 'X-Metadata': JSON.stringify(metadata) } : {},\n body: stream as any,\n });\n return;\n }\n\n // Synchronous iterable\n if (Symbol.iterator in Object(source ?? {})) {\n const it = (source as Iterable<any>)[Symbol.iterator]();\n const stream = new ReadableStream({\n pull(controller) {\n const res = it.next();\n if (res.done) { controller.close(); return; }\n controller.enqueue(RemoteStorage.normalizeChunk(res.value));\n }\n });\n await this.fetch(`${this.baseUrl}/api/storage/${encodeURIComponent(key)}`, {\n method: 'PUT',\n headers: metadata ? { 'X-Metadata': JSON.stringify(metadata) } : {},\n body: stream as any,\n });\n return;\n }\n\n // Fallback: primitive/string/Uint8Array/ArrayBuffer\n await this.put(key, RemoteStorage.normalizeChunk(source), metadata);\n }\n private static normalizeChunk(c: any): Uint8Array {\n if (c == null) return new Uint8Array();\n if (typeof c === 'string') return new TextEncoder().encode(c);\n if (c instanceof Uint8Array) return c;\n if (c instanceof ArrayBuffer) return new Uint8Array(c);\n if (Array.isArray(c)) return Uint8Array.from(c);\n throw new Error('Unsupported chunk type in putStream');\n }\n async delete(key: string): Promise<void> {\n await this.fetch(`${this.baseUrl}/api/storage/${encodeURIComponent(key)}`, {\n method: 'DELETE',\n });\n }\n\n async list(options?: { prefix?: string; limit?: number }): Promise<Array<{\n key: string;\n size: number;\n lastModified: string;\n metadata?: any;\n }>> {\n const params = new URLSearchParams();\n if (options?.prefix) params.set('prefix', options.prefix);\n if (options?.limit) params.set('limit', options.limit.toString());\n\n const response = await this.fetch(`${this.baseUrl}/api/storage?${params}`);\n return response.json();\n }\n\n async getMetadata(key: string): Promise<{\n size: number;\n contentType?: string;\n lastModified: string;\n metadata?: any;\n } | null> {\n const response = await this.fetch(`${this.baseUrl}/api/storage/${encodeURIComponent(key)}/metadata`);\n if (response.status === 404) return null;\n return response.json();\n }\n}\n\n/**\n * Create a remote platform client for development environments.\n * \n * This function creates a platform instance that communicates with a development\n * server over HTTP. It's used internally by getPlatform() when running in development\n * mode and no native platform is available.\n * \n * @param baseUrl - The base URL of the development server\n * @param tenant - The tenant identifier for multi-tenancy support\n * @returns A platform instance that proxies requests to the development server\n * \n * @internal\n * \n * @example\n * ```typescript\n * // This is typically called internally by getPlatform()\n * const platform = createRemoteClient('http://localhost:3001', 'dev-tenant');\n * \n * // The returned platform works the same as the native one\n * await platform.env.KV.cache.put('key', 'value');\n * await platform.env.DB.insertOne('users', { name: 'John' });\n * await platform.env.STORAGE.put('file.pdf', pdfData);\n * ```\n */\nexport function createRemoteClient(baseUrl: string, tenant: string) {\n const headers = {\n 'Content-Type': 'application/json',\n 'X-Tenant-Id': tenant,\n };\n\n // Create KV namespaces proxy that dynamically creates namespace instances\n const kvProxy = new Proxy({} as Record<string, KvNamespace>, {\n get(_, namespace: string) {\n return new RemoteKvNamespace(baseUrl, namespace, headers);\n }\n });\n\n const db = new RemoteDatabase(baseUrl, headers);\n const storage = new RemoteStorage(baseUrl, headers);\n\n return {\n env: {\n KV: kvProxy,\n DB: db,\n STORAGE: storage,\n }\n };\n}","/**\n * @fileoverview Maravilla Platform SDK\n * \n * This package provides the main interface for accessing Maravilla runtime services\n * including Key-Value storage and Database operations. It automatically detects the\n * runtime environment and provides the appropriate implementation.\n * \n * ## Environment Detection\n * \n * The SDK automatically detects and adapts to different environments:\n * - **Production**: Uses native Maravilla runtime APIs\n * - **Development**: Connects to development server via HTTP\n * - **Testing**: Can be mocked or use development server\n * \n * ## Key Features\n * \n * - **KV Storage**: Cloudflare Workers KV-compatible API for key-value operations\n * - **Database**: MongoDB-style document database operations\n * - **Multi-tenancy**: Built-in tenant isolation and support\n * - **TypeScript**: Fully typed APIs with comprehensive JSDoc comments\n * - **Development-friendly**: Seamless development server integration\n * \n * @example\n * ```typescript\n * import { getPlatform } from '@maravilla/platform';\n * \n * const platform = getPlatform();\n * \n * // KV operations\n * await platform.env.KV.cache.put('user:123', { name: 'John' });\n * const user = await platform.env.KV.cache.get('user:123');\n * \n * // Database operations\n * const userId = await platform.env.DB.insertOne('users', {\n * name: 'John Doe',\n * email: 'john@example.com'\n * });\n * \n * const users = await platform.env.DB.find('users', { active: true });\n * ```\n * \n * @author Maravilla Team\n * @since 1.0.0\n */\n\nimport type { Platform } from './types.js';\nimport { createRemoteClient } from './remote-client.js';\n\nexport * from './types.js';\n\n/**\n * Global platform instance injected by Maravilla runtime or development tools.\n * This should not be accessed directly - use getPlatform() instead.\n * \n * @internal\n */\n\ndeclare global {\n var __maravilla_platform: Platform | undefined;\n var platform: Platform | undefined;\n}\n\nlet cachedPlatform: Platform | null = null;\n\n/**\n * Get the platform instance. This will:\n * 1. Check if running in Maravilla runtime (global.platform exists)\n * 2. Check if a platform was injected via vite plugin or hooks\n * 3. Fall back to remote client for development\n * \n * The platform provides access to KV storage and database operations,\n * with automatic environment detection for seamless development and production.\n * \n * @param options - Optional configuration for development mode\n * @param options.devServerUrl - URL of the development server (defaults to MARAVILLA_DEV_SERVER env var or http://localhost:3001)\n * @param options.tenant - Tenant identifier for multi-tenancy (defaults to MARAVILLA_TENANT env var or 'dev-tenant')\n * @returns Platform instance with access to KV and database services\n * \n * @example\n * ```typescript\n * import { getPlatform } from '@maravilla/platform';\n * \n * // Basic usage (auto-detects environment)\n * const platform = getPlatform();\n * \n * // Development with custom server\n * const platform = getPlatform({\n * devServerUrl: 'http://localhost:3001',\n * tenant: 'my-app'\n * });\n * \n * // Use KV storage\n * await platform.env.KV.cache.put('user:123', { name: 'John' });\n * const user = await platform.env.KV.cache.get('user:123');\n * \n * // Use database\n * const userId = await platform.env.DB.insertOne('users', {\n * name: 'John Doe',\n * email: 'john@example.com'\n * });\n * ```\n */\nexport function getPlatform(options?: {\n devServerUrl?: string;\n tenant?: string;\n}): Platform {\n // Return cached if available\n if (cachedPlatform) {\n return cachedPlatform;\n }\n\n // 1. Check if we're in the real Maravilla runtime\n if (typeof globalThis !== 'undefined') {\n // Check for native platform (in production runtime)\n if (globalThis.platform) {\n console.log('[platform] Using native Maravilla platform');\n cachedPlatform = globalThis.platform;\n return cachedPlatform;\n }\n\n // Check for injected platform (from vite plugin or hooks)\n if (globalThis.__maravilla_platform) {\n console.log('[platform] Using injected platform');\n cachedPlatform = globalThis.__maravilla_platform;\n return cachedPlatform;\n }\n }\n\n // 2. Fall back to remote client for development\n const devServerUrl = options?.devServerUrl || process.env.MARAVILLA_DEV_SERVER || 'http://localhost:3001';\n const tenant = options?.tenant || process.env.MARAVILLA_TENANT || 'dev-tenant';\n \n console.log(`[platform] Creating remote client for ${devServerUrl}`);\n cachedPlatform = createRemoteClient(devServerUrl, tenant);\n \n // Cache it globally for other modules\n if (typeof globalThis !== 'undefined') {\n globalThis.__maravilla_platform = cachedPlatform;\n }\n \n return cachedPlatform;\n}\n\n/**\n * Clear the cached platform instance (useful for testing).\n * \n * This function resets the internal platform cache and clears any globally\n * injected platform instances. Subsequent calls to getPlatform() will\n * re-initialize the platform based on the current environment.\n * \n * @example\n * ```typescript\n * import { clearPlatformCache, getPlatform } from '@maravilla/platform';\n * \n * // In tests, clear cache between test cases\n * afterEach(() => {\n * clearPlatformCache();\n * });\n * \n * // Or when switching between different environments\n * clearPlatformCache();\n * const newPlatform = getPlatform({ devServerUrl: 'http://localhost:3002' });\n * ```\n */\nexport function clearPlatformCache(): void {\n cachedPlatform = null;\n if (typeof globalThis !== 'undefined') {\n globalThis.__maravilla_platform = undefined;\n }\n}"],"mappings":"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","names":[]}
|
package/package.json
CHANGED
package/src/remote-client.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { KvNamespace, KvListResult, Database, DbFindOptions } from './types.js';
|
|
1
|
+
import type { KvNamespace, KvListResult, Database, DbFindOptions, Storage } from './types.js';
|
|
2
2
|
|
|
3
3
|
/**
|
|
4
4
|
* Remote KV namespace implementation that communicates with a development server.
|
|
@@ -153,6 +153,197 @@ class RemoteDatabase implements Database {
|
|
|
153
153
|
}
|
|
154
154
|
}
|
|
155
155
|
|
|
156
|
+
/**
|
|
157
|
+
* Remote Storage implementation that communicates with a development server.
|
|
158
|
+
* This class provides the Storage interface by making HTTP requests to the dev server.
|
|
159
|
+
*
|
|
160
|
+
* @internal
|
|
161
|
+
*/
|
|
162
|
+
class RemoteStorage implements Storage {
|
|
163
|
+
constructor(
|
|
164
|
+
private baseUrl: string,
|
|
165
|
+
private headers: Record<string, string>
|
|
166
|
+
) {}
|
|
167
|
+
|
|
168
|
+
/**
|
|
169
|
+
* Internal method for making HTTP requests to the dev server.
|
|
170
|
+
* Handles error responses and authentication headers.
|
|
171
|
+
*
|
|
172
|
+
* @internal
|
|
173
|
+
*/
|
|
174
|
+
private async fetch(url: string, options: RequestInit = {}) {
|
|
175
|
+
const response = await fetch(url, {
|
|
176
|
+
...options,
|
|
177
|
+
headers: { ...this.headers, ...options.headers },
|
|
178
|
+
});
|
|
179
|
+
|
|
180
|
+
if (!response.ok && response.status !== 404) {
|
|
181
|
+
const error = await response.text();
|
|
182
|
+
throw new Error(`Platform API error: ${response.status} - ${error}`);
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
return response;
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
async generateUploadUrl(key: string, contentType: string, options?: { sizeLimit?: number }): Promise<{
|
|
189
|
+
url: string;
|
|
190
|
+
method: string;
|
|
191
|
+
headers: Record<string, string>;
|
|
192
|
+
expiresIn: number;
|
|
193
|
+
}> {
|
|
194
|
+
// For development, return a direct upload URL to the dev server
|
|
195
|
+
// The dev server supports PUT directly to /api/storage/{key}
|
|
196
|
+
return {
|
|
197
|
+
url: `${this.baseUrl}/api/storage/${encodeURIComponent(key)}`,
|
|
198
|
+
method: 'PUT',
|
|
199
|
+
headers: {
|
|
200
|
+
'Content-Type': contentType,
|
|
201
|
+
...this.headers, // Include tenant headers
|
|
202
|
+
},
|
|
203
|
+
expiresIn: 3600, // 1 hour
|
|
204
|
+
};
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
async generateDownloadUrl(key: string, options?: { expiresIn?: number }): Promise<{
|
|
208
|
+
url: string;
|
|
209
|
+
method: string;
|
|
210
|
+
headers: Record<string, string>;
|
|
211
|
+
expiresIn: number;
|
|
212
|
+
}> {
|
|
213
|
+
// For development, return a direct download URL to the dev server
|
|
214
|
+
// In production, this would call the actual storage service for presigned URLs
|
|
215
|
+
return {
|
|
216
|
+
url: `${this.baseUrl}/api/storage/${encodeURIComponent(key)}`,
|
|
217
|
+
method: 'GET',
|
|
218
|
+
headers: {},
|
|
219
|
+
expiresIn: options?.expiresIn || 3600, // Default 1 hour
|
|
220
|
+
};
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
async get(key: string): Promise<Uint8Array | null> {
|
|
224
|
+
const response = await this.fetch(`${this.baseUrl}/api/storage/${encodeURIComponent(key)}`);
|
|
225
|
+
if (response.status === 404) return null;
|
|
226
|
+
const arrayBuffer = await response.arrayBuffer();
|
|
227
|
+
return new Uint8Array(arrayBuffer);
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
async put(key: string, data: Uint8Array | string, metadata?: any): Promise<void> {
|
|
231
|
+
// Convert string to Uint8Array if needed
|
|
232
|
+
const bodyData = typeof data === 'string'
|
|
233
|
+
? new TextEncoder().encode(data)
|
|
234
|
+
: data;
|
|
235
|
+
|
|
236
|
+
await this.fetch(`${this.baseUrl}/api/storage/${encodeURIComponent(key)}`, {
|
|
237
|
+
method: 'PUT',
|
|
238
|
+
headers: metadata ? { 'X-Metadata': JSON.stringify(metadata) } : {},
|
|
239
|
+
// Cast because TypeScript's lib.dom.d.ts BodyInit union sometimes misses Uint8Array
|
|
240
|
+
body: bodyData as any,
|
|
241
|
+
});
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
async putStream(key: string, source: any, metadata?: any): Promise<void> {
|
|
245
|
+
// If it's a Blob we can send directly
|
|
246
|
+
if (typeof Blob !== 'undefined' && source instanceof Blob) {
|
|
247
|
+
await this.fetch(`${this.baseUrl}/api/storage/${encodeURIComponent(key)}`, {
|
|
248
|
+
method: 'PUT',
|
|
249
|
+
headers: {
|
|
250
|
+
...(metadata ? { 'X-Metadata': JSON.stringify(metadata) } : {}),
|
|
251
|
+
'Content-Type': source.type || 'application/octet-stream'
|
|
252
|
+
},
|
|
253
|
+
body: source as any,
|
|
254
|
+
});
|
|
255
|
+
return;
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
// ReadableStream
|
|
259
|
+
if (source && typeof source.getReader === 'function') {
|
|
260
|
+
await this.fetch(`${this.baseUrl}/api/storage/${encodeURIComponent(key)}`, {
|
|
261
|
+
method: 'PUT',
|
|
262
|
+
headers: metadata ? { 'X-Metadata': JSON.stringify(metadata) } : {},
|
|
263
|
+
body: source as any,
|
|
264
|
+
});
|
|
265
|
+
return;
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
// Async iterable -> wrap in ReadableStream
|
|
269
|
+
if (Symbol.asyncIterator in Object(source ?? {})) {
|
|
270
|
+
const asyncIt = source as AsyncIterable<any>;
|
|
271
|
+
const stream = new ReadableStream({
|
|
272
|
+
async pull(controller) {
|
|
273
|
+
const { value, done } = await asyncIt[Symbol.asyncIterator]().next();
|
|
274
|
+
if (done) { controller.close(); return; }
|
|
275
|
+
controller.enqueue(RemoteStorage.normalizeChunk(value));
|
|
276
|
+
}
|
|
277
|
+
});
|
|
278
|
+
await this.fetch(`${this.baseUrl}/api/storage/${encodeURIComponent(key)}`, {
|
|
279
|
+
method: 'PUT',
|
|
280
|
+
headers: metadata ? { 'X-Metadata': JSON.stringify(metadata) } : {},
|
|
281
|
+
body: stream as any,
|
|
282
|
+
});
|
|
283
|
+
return;
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
// Synchronous iterable
|
|
287
|
+
if (Symbol.iterator in Object(source ?? {})) {
|
|
288
|
+
const it = (source as Iterable<any>)[Symbol.iterator]();
|
|
289
|
+
const stream = new ReadableStream({
|
|
290
|
+
pull(controller) {
|
|
291
|
+
const res = it.next();
|
|
292
|
+
if (res.done) { controller.close(); return; }
|
|
293
|
+
controller.enqueue(RemoteStorage.normalizeChunk(res.value));
|
|
294
|
+
}
|
|
295
|
+
});
|
|
296
|
+
await this.fetch(`${this.baseUrl}/api/storage/${encodeURIComponent(key)}`, {
|
|
297
|
+
method: 'PUT',
|
|
298
|
+
headers: metadata ? { 'X-Metadata': JSON.stringify(metadata) } : {},
|
|
299
|
+
body: stream as any,
|
|
300
|
+
});
|
|
301
|
+
return;
|
|
302
|
+
}
|
|
303
|
+
|
|
304
|
+
// Fallback: primitive/string/Uint8Array/ArrayBuffer
|
|
305
|
+
await this.put(key, RemoteStorage.normalizeChunk(source), metadata);
|
|
306
|
+
}
|
|
307
|
+
private static normalizeChunk(c: any): Uint8Array {
|
|
308
|
+
if (c == null) return new Uint8Array();
|
|
309
|
+
if (typeof c === 'string') return new TextEncoder().encode(c);
|
|
310
|
+
if (c instanceof Uint8Array) return c;
|
|
311
|
+
if (c instanceof ArrayBuffer) return new Uint8Array(c);
|
|
312
|
+
if (Array.isArray(c)) return Uint8Array.from(c);
|
|
313
|
+
throw new Error('Unsupported chunk type in putStream');
|
|
314
|
+
}
|
|
315
|
+
async delete(key: string): Promise<void> {
|
|
316
|
+
await this.fetch(`${this.baseUrl}/api/storage/${encodeURIComponent(key)}`, {
|
|
317
|
+
method: 'DELETE',
|
|
318
|
+
});
|
|
319
|
+
}
|
|
320
|
+
|
|
321
|
+
async list(options?: { prefix?: string; limit?: number }): Promise<Array<{
|
|
322
|
+
key: string;
|
|
323
|
+
size: number;
|
|
324
|
+
lastModified: string;
|
|
325
|
+
metadata?: any;
|
|
326
|
+
}>> {
|
|
327
|
+
const params = new URLSearchParams();
|
|
328
|
+
if (options?.prefix) params.set('prefix', options.prefix);
|
|
329
|
+
if (options?.limit) params.set('limit', options.limit.toString());
|
|
330
|
+
|
|
331
|
+
const response = await this.fetch(`${this.baseUrl}/api/storage?${params}`);
|
|
332
|
+
return response.json();
|
|
333
|
+
}
|
|
334
|
+
|
|
335
|
+
async getMetadata(key: string): Promise<{
|
|
336
|
+
size: number;
|
|
337
|
+
contentType?: string;
|
|
338
|
+
lastModified: string;
|
|
339
|
+
metadata?: any;
|
|
340
|
+
} | null> {
|
|
341
|
+
const response = await this.fetch(`${this.baseUrl}/api/storage/${encodeURIComponent(key)}/metadata`);
|
|
342
|
+
if (response.status === 404) return null;
|
|
343
|
+
return response.json();
|
|
344
|
+
}
|
|
345
|
+
}
|
|
346
|
+
|
|
156
347
|
/**
|
|
157
348
|
* Create a remote platform client for development environments.
|
|
158
349
|
*
|
|
@@ -174,6 +365,7 @@ class RemoteDatabase implements Database {
|
|
|
174
365
|
* // The returned platform works the same as the native one
|
|
175
366
|
* await platform.env.KV.cache.put('key', 'value');
|
|
176
367
|
* await platform.env.DB.insertOne('users', { name: 'John' });
|
|
368
|
+
* await platform.env.STORAGE.put('file.pdf', pdfData);
|
|
177
369
|
* ```
|
|
178
370
|
*/
|
|
179
371
|
export function createRemoteClient(baseUrl: string, tenant: string) {
|
|
@@ -190,11 +382,13 @@ export function createRemoteClient(baseUrl: string, tenant: string) {
|
|
|
190
382
|
});
|
|
191
383
|
|
|
192
384
|
const db = new RemoteDatabase(baseUrl, headers);
|
|
385
|
+
const storage = new RemoteStorage(baseUrl, headers);
|
|
193
386
|
|
|
194
387
|
return {
|
|
195
388
|
env: {
|
|
196
389
|
KV: kvProxy,
|
|
197
390
|
DB: db,
|
|
391
|
+
STORAGE: storage,
|
|
198
392
|
}
|
|
199
393
|
};
|
|
200
394
|
}
|
package/src/types.ts
CHANGED
|
@@ -263,9 +263,247 @@ export interface DbFindOptions {
|
|
|
263
263
|
sort?: any;
|
|
264
264
|
}
|
|
265
265
|
|
|
266
|
+
/**
|
|
267
|
+
* Storage interface for object/file storage operations.
|
|
268
|
+
* Provides S3-compatible API for storing and retrieving files.
|
|
269
|
+
*
|
|
270
|
+
* @example
|
|
271
|
+
* ```typescript
|
|
272
|
+
* const platform = getPlatform();
|
|
273
|
+
* const storage = platform.env.STORAGE;
|
|
274
|
+
*
|
|
275
|
+
* // Generate presigned upload URL for direct browser upload
|
|
276
|
+
* const uploadUrl = await storage.generateUploadUrl(
|
|
277
|
+
* 'profile-pics/user123.jpg',
|
|
278
|
+
* 'image/jpeg',
|
|
279
|
+
* { sizeLimit: 5 * 1024 * 1024 } // 5MB limit
|
|
280
|
+
* );
|
|
281
|
+
*
|
|
282
|
+
* // Generate download URL
|
|
283
|
+
* const downloadUrl = await storage.generateDownloadUrl(
|
|
284
|
+
* 'profile-pics/user123.jpg',
|
|
285
|
+
* { expiresIn: 3600 } // 1 hour
|
|
286
|
+
* );
|
|
287
|
+
*
|
|
288
|
+
* // Direct storage operations
|
|
289
|
+
* await storage.put('docs/report.pdf', pdfData, { contentType: 'application/pdf' });
|
|
290
|
+
* const file = await storage.get('docs/report.pdf');
|
|
291
|
+
* ```
|
|
292
|
+
*/
|
|
293
|
+
/**
|
|
294
|
+
* Source types accepted by Storage.putStream for streaming uploads.
|
|
295
|
+
*/
|
|
296
|
+
export type StoragePutStreamSource =
|
|
297
|
+
| Uint8Array
|
|
298
|
+
| ArrayBuffer
|
|
299
|
+
| string
|
|
300
|
+
| Blob
|
|
301
|
+
| ReadableStream<Uint8Array | ArrayBuffer | string | number[]>
|
|
302
|
+
| Iterable<Uint8Array | ArrayBuffer | string | number[]>
|
|
303
|
+
| AsyncIterable<Uint8Array | ArrayBuffer | string | number[]>;
|
|
304
|
+
|
|
305
|
+
export interface Storage {
|
|
306
|
+
/**
|
|
307
|
+
* Generate a presigned URL for uploading a file directly from the browser.
|
|
308
|
+
*
|
|
309
|
+
* @param key - The storage key/path for the file
|
|
310
|
+
* @param contentType - MIME type of the file
|
|
311
|
+
* @param options - Upload options
|
|
312
|
+
* @param options.sizeLimit - Maximum file size in bytes
|
|
313
|
+
* @returns Promise that resolves to upload URL details
|
|
314
|
+
*
|
|
315
|
+
* @example
|
|
316
|
+
* ```typescript
|
|
317
|
+
* const upload = await storage.generateUploadUrl(
|
|
318
|
+
* 'uploads/document.pdf',
|
|
319
|
+
* 'application/pdf',
|
|
320
|
+
* { sizeLimit: 10 * 1024 * 1024 } // 10MB
|
|
321
|
+
* );
|
|
322
|
+
*
|
|
323
|
+
* // Use the URL to upload from browser
|
|
324
|
+
* await fetch(upload.url, {
|
|
325
|
+
* method: upload.method,
|
|
326
|
+
* headers: upload.headers,
|
|
327
|
+
* body: fileData
|
|
328
|
+
* });
|
|
329
|
+
* ```
|
|
330
|
+
*/
|
|
331
|
+
generateUploadUrl(key: string, contentType: string, options?: { sizeLimit?: number }): Promise<{
|
|
332
|
+
url: string;
|
|
333
|
+
method: string;
|
|
334
|
+
headers: Record<string, string>;
|
|
335
|
+
expiresIn: number;
|
|
336
|
+
}>;
|
|
337
|
+
|
|
338
|
+
/**
|
|
339
|
+
* Generate a presigned URL for downloading a file.
|
|
340
|
+
*
|
|
341
|
+
* @param key - The storage key/path for the file
|
|
342
|
+
* @param options - Download options
|
|
343
|
+
* @param options.expiresIn - URL expiration time in seconds (default: 3600)
|
|
344
|
+
* @returns Promise that resolves to download URL details
|
|
345
|
+
*
|
|
346
|
+
* @example
|
|
347
|
+
* ```typescript
|
|
348
|
+
* const download = await storage.generateDownloadUrl(
|
|
349
|
+
* 'uploads/document.pdf',
|
|
350
|
+
* { expiresIn: 7200 } // 2 hours
|
|
351
|
+
* );
|
|
352
|
+
*
|
|
353
|
+
* // Provide URL to client for download
|
|
354
|
+
* return { downloadUrl: download.url };
|
|
355
|
+
* ```
|
|
356
|
+
*/
|
|
357
|
+
generateDownloadUrl(key: string, options?: { expiresIn?: number }): Promise<{
|
|
358
|
+
url: string;
|
|
359
|
+
method: string;
|
|
360
|
+
headers: Record<string, string>;
|
|
361
|
+
expiresIn: number;
|
|
362
|
+
}>;
|
|
363
|
+
|
|
364
|
+
/**
|
|
365
|
+
* Retrieve a file from storage.
|
|
366
|
+
*
|
|
367
|
+
* @param key - The storage key/path for the file
|
|
368
|
+
* @returns Promise that resolves to the file data, or null if not found
|
|
369
|
+
*
|
|
370
|
+
* @example
|
|
371
|
+
* ```typescript
|
|
372
|
+
* const imageData = await storage.get('profile-pics/user123.jpg');
|
|
373
|
+
* if (imageData) {
|
|
374
|
+
* // Process the image data
|
|
375
|
+
* return new Response(imageData, {
|
|
376
|
+
* headers: { 'Content-Type': 'image/jpeg' }
|
|
377
|
+
* });
|
|
378
|
+
* }
|
|
379
|
+
* ```
|
|
380
|
+
*/
|
|
381
|
+
get(key: string): Promise<Uint8Array | null>;
|
|
382
|
+
|
|
383
|
+
/**
|
|
384
|
+
* Store a file in storage.
|
|
385
|
+
*
|
|
386
|
+
* @param key - The storage key/path for the file
|
|
387
|
+
* @param data - The file data to store
|
|
388
|
+
* @param metadata - Optional metadata for the file
|
|
389
|
+
* @returns Promise that resolves when the operation completes
|
|
390
|
+
*
|
|
391
|
+
* @example
|
|
392
|
+
* ```typescript
|
|
393
|
+
* // Store an image with metadata
|
|
394
|
+
* await storage.put(
|
|
395
|
+
* 'profile-pics/user123.jpg',
|
|
396
|
+
* imageData,
|
|
397
|
+
* {
|
|
398
|
+
* contentType: 'image/jpeg',
|
|
399
|
+
* userId: 'user123',
|
|
400
|
+
* uploadedAt: new Date().toISOString()
|
|
401
|
+
* }
|
|
402
|
+
* );
|
|
403
|
+
* ```
|
|
404
|
+
*/
|
|
405
|
+
put(key: string, data: Uint8Array | string, metadata?: any): Promise<void>;
|
|
406
|
+
|
|
407
|
+
/**
|
|
408
|
+
* Store a file in storage using streaming/chunked data sources.
|
|
409
|
+
*
|
|
410
|
+
* Accepts a variety of source types (ReadableStream, Blob, ArrayBuffer,
|
|
411
|
+
* Uint8Array, string, or (async) iterables of chunks) and efficiently
|
|
412
|
+
* uploads them without requiring the caller to fully buffer the file
|
|
413
|
+
* in memory first when running in the native runtime. In the remote
|
|
414
|
+
* development client this currently buffers in memory as a fallback.
|
|
415
|
+
*
|
|
416
|
+
* Chunk types inside iterables can be Uint8Array, ArrayBuffer, number[]
|
|
417
|
+
* or string; strings are UTF-8 encoded.
|
|
418
|
+
*
|
|
419
|
+
* @param key - The storage key/path for the file
|
|
420
|
+
* @param source - Streaming or buffered data source
|
|
421
|
+
* @param metadata - Optional metadata for the file
|
|
422
|
+
* @returns Promise that resolves when the operation completes
|
|
423
|
+
*
|
|
424
|
+
* @example
|
|
425
|
+
* ```typescript
|
|
426
|
+
* // Upload a Blob (e.g. from a file input in a web app)
|
|
427
|
+
* await storage.putStream('videos/clip.mp4', fileBlob, { contentType: fileBlob.type });
|
|
428
|
+
*
|
|
429
|
+
* // Upload using an async generator that yields chunks
|
|
430
|
+
* async function *generate() {
|
|
431
|
+
* for await (const piece of someAsyncSource) {
|
|
432
|
+
* yield piece; // Uint8Array | string | ArrayBuffer
|
|
433
|
+
* }
|
|
434
|
+
* }
|
|
435
|
+
* await storage.putStream('logs/large.txt', generate(), { contentType: 'text/plain' });
|
|
436
|
+
* ```
|
|
437
|
+
*/
|
|
438
|
+
putStream(
|
|
439
|
+
key: string,
|
|
440
|
+
source: StoragePutStreamSource,
|
|
441
|
+
metadata?: any
|
|
442
|
+
): Promise<void>;
|
|
443
|
+
|
|
444
|
+
/**
|
|
445
|
+
* Delete a file from storage.
|
|
446
|
+
*
|
|
447
|
+
* @param key - The storage key/path for the file
|
|
448
|
+
* @returns Promise that resolves when the operation completes
|
|
449
|
+
*
|
|
450
|
+
* @example
|
|
451
|
+
* ```typescript
|
|
452
|
+
* await storage.delete('profile-pics/old-photo.jpg');
|
|
453
|
+
* ```
|
|
454
|
+
*/
|
|
455
|
+
delete(key: string): Promise<void>;
|
|
456
|
+
|
|
457
|
+
/**
|
|
458
|
+
* List files in storage with optional prefix filtering.
|
|
459
|
+
*
|
|
460
|
+
* @param options - Listing options
|
|
461
|
+
* @param options.prefix - Only return keys that start with this prefix
|
|
462
|
+
* @param options.limit - Maximum number of keys to return
|
|
463
|
+
* @returns Promise that resolves to list of file keys and metadata
|
|
464
|
+
*
|
|
465
|
+
* @example
|
|
466
|
+
* ```typescript
|
|
467
|
+
* // List all profile pictures
|
|
468
|
+
* const pics = await storage.list({ prefix: 'profile-pics/' });
|
|
469
|
+
*
|
|
470
|
+
* // List with pagination
|
|
471
|
+
* const files = await storage.list({ prefix: 'docs/', limit: 20 });
|
|
472
|
+
* ```
|
|
473
|
+
*/
|
|
474
|
+
list(options?: { prefix?: string; limit?: number }): Promise<Array<{
|
|
475
|
+
key: string;
|
|
476
|
+
size: number;
|
|
477
|
+
lastModified: string;
|
|
478
|
+
metadata?: any;
|
|
479
|
+
}>>;
|
|
480
|
+
|
|
481
|
+
/**
|
|
482
|
+
* Get metadata for a file without retrieving its contents.
|
|
483
|
+
*
|
|
484
|
+
* @param key - The storage key/path for the file
|
|
485
|
+
* @returns Promise that resolves to file metadata, or null if not found
|
|
486
|
+
*
|
|
487
|
+
* @example
|
|
488
|
+
* ```typescript
|
|
489
|
+
* const meta = await storage.getMetadata('uploads/document.pdf');
|
|
490
|
+
* if (meta) {
|
|
491
|
+
* console.log(`File size: ${meta.size} bytes`);
|
|
492
|
+
* console.log(`Last modified: ${meta.lastModified}`);
|
|
493
|
+
* }
|
|
494
|
+
* ```
|
|
495
|
+
*/
|
|
496
|
+
getMetadata(key: string): Promise<{
|
|
497
|
+
size: number;
|
|
498
|
+
contentType?: string;
|
|
499
|
+
lastModified: string;
|
|
500
|
+
metadata?: any;
|
|
501
|
+
} | null>;
|
|
502
|
+
}
|
|
503
|
+
|
|
266
504
|
/**
|
|
267
505
|
* Platform environment containing all available services.
|
|
268
|
-
* This is the main interface for accessing KV storage and
|
|
506
|
+
* This is the main interface for accessing KV storage, database, and object storage operations.
|
|
269
507
|
*/
|
|
270
508
|
export interface PlatformEnv {
|
|
271
509
|
/**
|
|
@@ -280,6 +518,8 @@ export interface PlatformEnv {
|
|
|
280
518
|
KV: Record<string, KvNamespace>;
|
|
281
519
|
/** Database interface for document operations */
|
|
282
520
|
DB: Database;
|
|
521
|
+
/** Object/file storage interface */
|
|
522
|
+
STORAGE: Storage;
|
|
283
523
|
}
|
|
284
524
|
|
|
285
525
|
/**
|