@maravilla-labs/platform 0.1.18 → 0.1.21
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 +273 -2
- package/dist/index.js +319 -2
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
- package/src/index.ts +1 -0
- package/src/remote-client.ts +195 -1
- package/src/ren.ts +214 -0
- package/src/types.ts +241 -1
- package/tsconfig.json +1 -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$1 {
|
|
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$1;
|
|
283
510
|
}
|
|
284
511
|
/**
|
|
285
512
|
* Main platform interface providing access to all Maravilla runtime services.
|
|
@@ -303,6 +530,50 @@ interface Platform {
|
|
|
303
530
|
env: PlatformEnv;
|
|
304
531
|
}
|
|
305
532
|
|
|
533
|
+
interface RenEvent {
|
|
534
|
+
t: string;
|
|
535
|
+
r: string;
|
|
536
|
+
k?: string;
|
|
537
|
+
v?: string;
|
|
538
|
+
ts?: number;
|
|
539
|
+
src?: string;
|
|
540
|
+
ns?: string;
|
|
541
|
+
[extra: string]: any;
|
|
542
|
+
}
|
|
543
|
+
interface RenClientOptions {
|
|
544
|
+
endpoint?: string;
|
|
545
|
+
subscriptions?: string[];
|
|
546
|
+
clientId?: string;
|
|
547
|
+
autoReconnect?: boolean;
|
|
548
|
+
maxBackoffMs?: number;
|
|
549
|
+
debug?: boolean;
|
|
550
|
+
}
|
|
551
|
+
type Listener = (event: RenEvent) => void;
|
|
552
|
+
declare class RenClient {
|
|
553
|
+
private endpoint;
|
|
554
|
+
private subs;
|
|
555
|
+
private clientId;
|
|
556
|
+
private listeners;
|
|
557
|
+
private es;
|
|
558
|
+
private closed;
|
|
559
|
+
private attempt;
|
|
560
|
+
private autoReconnect;
|
|
561
|
+
private maxBackoff;
|
|
562
|
+
private debug;
|
|
563
|
+
constructor(opts?: RenClientOptions);
|
|
564
|
+
private detectEndpoint;
|
|
565
|
+
private log;
|
|
566
|
+
private buildUrl;
|
|
567
|
+
private connect;
|
|
568
|
+
on(listener: Listener): () => void;
|
|
569
|
+
getClientId(): string;
|
|
570
|
+
close(): void;
|
|
571
|
+
}
|
|
572
|
+
declare function getOrCreateClientId(storage?: Storage): string;
|
|
573
|
+
declare function renFetch(input: string | URL | Request, init?: RequestInit, clientId?: string): Promise<Response>;
|
|
574
|
+
declare function storageUpload(path: string, file: Blob | File, clientId?: string): Promise<any>;
|
|
575
|
+
declare function storageDelete(path: string, clientId?: string): Promise<any>;
|
|
576
|
+
|
|
306
577
|
/**
|
|
307
578
|
* @fileoverview Maravilla Platform SDK
|
|
308
579
|
*
|
|
@@ -423,4 +694,4 @@ declare function getPlatform(options?: {
|
|
|
423
694
|
*/
|
|
424
695
|
declare function clearPlatformCache(): void;
|
|
425
696
|
|
|
426
|
-
export { type Database, type DbFindOptions, type KvListResult, type KvNamespace, type Platform, type PlatformEnv, clearPlatformCache, getPlatform };
|
|
697
|
+
export { type Database, type DbFindOptions, type KvListResult, type KvNamespace, type Platform, type PlatformEnv, RenClient, type RenClientOptions, type RenEvent, type Storage$1 as Storage, type StoragePutStreamSource, clearPlatformCache, getOrCreateClientId, getPlatform, renFetch, storageDelete, storageUpload };
|
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,14 +275,181 @@ 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
|
}
|
|
140
287
|
|
|
288
|
+
// src/ren.ts
|
|
289
|
+
var RenClient = class {
|
|
290
|
+
endpoint;
|
|
291
|
+
subs;
|
|
292
|
+
clientId;
|
|
293
|
+
listeners = /* @__PURE__ */ new Set();
|
|
294
|
+
es = null;
|
|
295
|
+
closed = false;
|
|
296
|
+
attempt = 0;
|
|
297
|
+
autoReconnect;
|
|
298
|
+
maxBackoff;
|
|
299
|
+
debug;
|
|
300
|
+
constructor(opts = {}) {
|
|
301
|
+
this.endpoint = opts.endpoint || this.detectEndpoint();
|
|
302
|
+
this.subs = opts.subscriptions && opts.subscriptions.length ? opts.subscriptions : ["*"];
|
|
303
|
+
this.clientId = opts.clientId || getOrCreateClientId();
|
|
304
|
+
this.autoReconnect = opts.autoReconnect !== false;
|
|
305
|
+
this.maxBackoff = opts.maxBackoffMs ?? 15e3;
|
|
306
|
+
this.debug = !!opts.debug || typeof localStorage !== "undefined" && localStorage.getItem("REN_DEBUG") === "1";
|
|
307
|
+
this.connect();
|
|
308
|
+
}
|
|
309
|
+
detectEndpoint() {
|
|
310
|
+
console.log("[REN detectEndpoint] Starting detection...");
|
|
311
|
+
console.log("[REN detectEndpoint] globalThis:", typeof globalThis);
|
|
312
|
+
console.log("[REN detectEndpoint] globalThis.platform:", globalThis?.platform);
|
|
313
|
+
console.log("[REN detectEndpoint] globalThis.__maravilla_platform:", globalThis?.__maravilla_platform);
|
|
314
|
+
console.log("[REN detectEndpoint] window:", typeof window);
|
|
315
|
+
console.log("[REN detectEndpoint] window.location:", typeof window !== "undefined" ? window.location.href : "N/A");
|
|
316
|
+
if (typeof globalThis !== "undefined" && globalThis.platform) {
|
|
317
|
+
console.log("[REN detectEndpoint] Detected production runtime, using relative endpoint");
|
|
318
|
+
return "/api/maravilla/ren";
|
|
319
|
+
}
|
|
320
|
+
if (typeof window !== "undefined" && window.location.port === "5173") {
|
|
321
|
+
const devServerUrl = `http://${window.location.hostname}:3001`;
|
|
322
|
+
console.log("[REN detectEndpoint] Detected Vite dev server (port 5173), using dev server:", devServerUrl);
|
|
323
|
+
return `${devServerUrl}/api/maravilla/ren`;
|
|
324
|
+
}
|
|
325
|
+
if (typeof globalThis !== "undefined" && globalThis.__maravilla_platform) {
|
|
326
|
+
let devServerUrl = "http://localhost:3001";
|
|
327
|
+
if (typeof window !== "undefined" && window.location.hostname !== "localhost") {
|
|
328
|
+
devServerUrl = `http://${window.location.hostname}:3001`;
|
|
329
|
+
}
|
|
330
|
+
console.log("[REN detectEndpoint] Detected injected platform, using dev server:", devServerUrl);
|
|
331
|
+
return `${devServerUrl}/api/maravilla/ren`;
|
|
332
|
+
}
|
|
333
|
+
console.log("[REN detectEndpoint] Using default relative endpoint (preview/production mode)");
|
|
334
|
+
return "/api/maravilla/ren";
|
|
335
|
+
}
|
|
336
|
+
log(...args) {
|
|
337
|
+
if (this.debug) console.debug("[RenClient]", ...args);
|
|
338
|
+
}
|
|
339
|
+
buildUrl() {
|
|
340
|
+
const s = this.subs.length === 1 && this.subs[0] === "*" ? "*" : this.subs.join(",");
|
|
341
|
+
const qs = new URLSearchParams({ cid: this.clientId, s });
|
|
342
|
+
return `${this.endpoint}?${qs.toString()}`;
|
|
343
|
+
}
|
|
344
|
+
connect() {
|
|
345
|
+
const url = this.buildUrl();
|
|
346
|
+
this.log("connecting", { url });
|
|
347
|
+
this.es = new EventSource(url);
|
|
348
|
+
this.closed = false;
|
|
349
|
+
const forward = (e) => {
|
|
350
|
+
try {
|
|
351
|
+
const evt = JSON.parse(e.data);
|
|
352
|
+
this.log("event", evt);
|
|
353
|
+
this.listeners.forEach((l) => l(evt));
|
|
354
|
+
} catch (err) {
|
|
355
|
+
this.log("malformed event data", e.data, err);
|
|
356
|
+
}
|
|
357
|
+
};
|
|
358
|
+
this.es.onmessage = forward;
|
|
359
|
+
const knownEventTypes = [
|
|
360
|
+
// Database events
|
|
361
|
+
"db.document.created",
|
|
362
|
+
"db.document.updated",
|
|
363
|
+
"db.document.deleted",
|
|
364
|
+
// KV events
|
|
365
|
+
"kv.put",
|
|
366
|
+
"kv.delete",
|
|
367
|
+
"kv.expired",
|
|
368
|
+
// Storage events
|
|
369
|
+
"storage.object.created",
|
|
370
|
+
"storage.object.updated",
|
|
371
|
+
"storage.object.deleted",
|
|
372
|
+
// Runtime events
|
|
373
|
+
"runtime.snapshot.ready",
|
|
374
|
+
"runtime.worker.started",
|
|
375
|
+
"runtime.worker.stopped",
|
|
376
|
+
// Meta events
|
|
377
|
+
"ren.meta"
|
|
378
|
+
];
|
|
379
|
+
knownEventTypes.forEach((k) => this.es?.addEventListener(k, forward));
|
|
380
|
+
this.es.onerror = (ev) => {
|
|
381
|
+
this.log("error", ev);
|
|
382
|
+
if (this.closed) return;
|
|
383
|
+
this.es?.close();
|
|
384
|
+
if (!this.autoReconnect) return;
|
|
385
|
+
const delay = Math.min(1e3 * Math.pow(2, this.attempt++), this.maxBackoff);
|
|
386
|
+
this.log("reconnecting in", delay, "ms");
|
|
387
|
+
setTimeout(() => this.connect(), delay);
|
|
388
|
+
};
|
|
389
|
+
this.es.onopen = () => {
|
|
390
|
+
this.attempt = 0;
|
|
391
|
+
this.log("open");
|
|
392
|
+
};
|
|
393
|
+
}
|
|
394
|
+
on(listener) {
|
|
395
|
+
this.listeners.add(listener);
|
|
396
|
+
this.log("listener added; total", this.listeners.size);
|
|
397
|
+
return () => {
|
|
398
|
+
this.listeners.delete(listener);
|
|
399
|
+
this.log("listener removed; total", this.listeners.size);
|
|
400
|
+
};
|
|
401
|
+
}
|
|
402
|
+
getClientId() {
|
|
403
|
+
return this.clientId;
|
|
404
|
+
}
|
|
405
|
+
close() {
|
|
406
|
+
this.closed = true;
|
|
407
|
+
this.es?.close();
|
|
408
|
+
this.log("closed");
|
|
409
|
+
}
|
|
410
|
+
};
|
|
411
|
+
function getOrCreateClientId(storage) {
|
|
412
|
+
if (!storage) {
|
|
413
|
+
storage = typeof window !== "undefined" && window.localStorage ? window.localStorage : typeof globalThis !== "undefined" && globalThis.localStorage ? globalThis.localStorage : void 0;
|
|
414
|
+
if (!storage) {
|
|
415
|
+
return globalThis.crypto?.randomUUID?.() || randomFallback();
|
|
416
|
+
}
|
|
417
|
+
}
|
|
418
|
+
const key = "maravillaClientId";
|
|
419
|
+
let id = storage.getItem(key);
|
|
420
|
+
if (!id) {
|
|
421
|
+
id = globalThis.crypto?.randomUUID?.() || randomFallback();
|
|
422
|
+
try {
|
|
423
|
+
storage.setItem(key, id);
|
|
424
|
+
} catch {
|
|
425
|
+
}
|
|
426
|
+
}
|
|
427
|
+
return id;
|
|
428
|
+
}
|
|
429
|
+
function randomFallback() {
|
|
430
|
+
return "xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx".replace(/[xy]/g, (c) => {
|
|
431
|
+
const r = Math.random() * 16 | 0;
|
|
432
|
+
const v = c === "x" ? r : r & 3 | 8;
|
|
433
|
+
return v.toString(16);
|
|
434
|
+
});
|
|
435
|
+
}
|
|
436
|
+
async function renFetch(input, init = {}, clientId) {
|
|
437
|
+
const cid = clientId || getOrCreateClientId();
|
|
438
|
+
const headers = new Headers(init.headers || {});
|
|
439
|
+
headers.set("X-Ren-Client", cid);
|
|
440
|
+
return fetch(input, { ...init, headers });
|
|
441
|
+
}
|
|
442
|
+
async function storageUpload(path, file, clientId) {
|
|
443
|
+
const res = await renFetch(`/api/storage/upload?path=${encodeURIComponent(path)}`, { method: "POST", body: file }, clientId);
|
|
444
|
+
if (!res.ok) throw new Error("upload failed");
|
|
445
|
+
return res.json().catch(() => ({}));
|
|
446
|
+
}
|
|
447
|
+
async function storageDelete(path, clientId) {
|
|
448
|
+
const res = await renFetch(`/api/storage/delete?path=${encodeURIComponent(path)}`, { method: "DELETE" }, clientId);
|
|
449
|
+
if (!res.ok) throw new Error("delete failed");
|
|
450
|
+
return res.json().catch(() => ({}));
|
|
451
|
+
}
|
|
452
|
+
|
|
141
453
|
// src/index.ts
|
|
142
454
|
var cachedPlatform = null;
|
|
143
455
|
function getPlatform(options) {
|
|
@@ -172,7 +484,12 @@ function clearPlatformCache() {
|
|
|
172
484
|
}
|
|
173
485
|
}
|
|
174
486
|
export {
|
|
487
|
+
RenClient,
|
|
175
488
|
clearPlatformCache,
|
|
176
|
-
|
|
489
|
+
getOrCreateClientId,
|
|
490
|
+
getPlatform,
|
|
491
|
+
renFetch,
|
|
492
|
+
storageDelete,
|
|
493
|
+
storageUpload
|
|
177
494
|
};
|
|
178
495
|
//# sourceMappingURL=index.js.map
|