flingit 0.0.52 → 0.0.54

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.
@@ -457,6 +457,30 @@ if (file) {
457
457
 
458
458
  **Returns:** `StorageObjectBody | null` (null if not found)
459
459
 
460
+ ### Range Gets (Partial Reads)
461
+
462
+ Read a byte range from an object (e.g., seeking into audio/video files):
463
+
464
+ ```typescript
465
+ const chunk = await storage.get("audio/song.mp3", {
466
+ range: { offset: 1000, length: 500 },
467
+ });
468
+
469
+ if (chunk) {
470
+ chunk.size; // full object size (not the range size)
471
+ chunk.range; // { offset: 1000, length: 500 } — actual bytes returned
472
+ const bytes = await chunk.arrayBuffer(); // only the ranged bytes
473
+ }
474
+ ```
475
+
476
+ **Range rules:**
477
+ - Both `offset` and `length` are **required** when specifying a range
478
+ - `offset` and `length` must be non-negative integers
479
+ - If `length` extends past the end of the object, it is clamped to the available bytes
480
+ - If `offset` is beyond the object size, an error is thrown (except `offset == size` with `length == 0`, which returns an empty body)
481
+ - `size` always reports the full object size; range size is in `result.range.length`
482
+ - The `range` property is only present on the result when a range was requested
483
+
460
484
  ### Checking Existence (Head)
461
485
 
462
486
  Get metadata without downloading the content:
@@ -583,6 +607,97 @@ npx fling -- --prod storage info # Production R2 stats
583
607
  - **5GB max object size** for direct uploads (streamed, not buffered)
584
608
  - **Streaming** - Large files are streamed, not buffered in memory
585
609
 
610
+ ### Presigned URLs (Direct Browser Upload/Download)
611
+
612
+ For large files (up to 5GB), create presigned URLs that let browsers upload/download directly to/from R2, bypassing the worker's 100MB request limit.
613
+
614
+ #### `storage.createUploadUrl(key, options?)`
615
+
616
+ Create a presigned URL for uploading a file directly to storage.
617
+
618
+ ```typescript
619
+ import { app, storage } from "flingit";
620
+
621
+ // Worker endpoint that generates an upload URL
622
+ app.post("/api/upload-url", async (c) => {
623
+ const { filename, contentType } = await c.req.json();
624
+ const key = `uploads/${Date.now()}-${filename}`;
625
+
626
+ const { url, expiresAt } = await storage.createUploadUrl(key, {
627
+ expiresIn: 600, // URL valid for 10 minutes (default: 3600)
628
+ contentType, // informational, NOT signed into URL
629
+ });
630
+
631
+ return c.json({ url, key, expiresAt: expiresAt.toISOString() });
632
+ });
633
+ ```
634
+
635
+ Frontend code to use the upload URL:
636
+
637
+ ```typescript
638
+ // Get presigned URL from your API
639
+ const { url, key } = await fetch("/api/upload-url", {
640
+ method: "POST",
641
+ headers: { "Content-Type": "application/json" },
642
+ body: JSON.stringify({ filename: file.name, contentType: file.type }),
643
+ }).then(r => r.json());
644
+
645
+ // Upload directly to R2 (bypasses worker)
646
+ await fetch(url, {
647
+ method: "PUT",
648
+ body: file,
649
+ headers: { "Content-Type": file.type },
650
+ });
651
+ ```
652
+
653
+ **Options (`CreateUploadUrlOptions`):**
654
+ | Option | Type | Default | Description |
655
+ |--------|------|---------|-------------|
656
+ | `expiresIn` | `number` | `3600` | URL expiry in seconds (clamped to credential lifetime) |
657
+ | `contentType` | `string` | — | Informational only; NOT signed into URL |
658
+
659
+ #### `storage.createDownloadUrl(key, options?)`
660
+
661
+ Create a presigned URL for downloading a file directly from storage.
662
+
663
+ ```typescript
664
+ // Redirect pattern — browser downloads directly from R2
665
+ app.get("/api/download/:key", async (c) => {
666
+ const key = c.req.param("key");
667
+ const { url } = await storage.createDownloadUrl(key, {
668
+ expiresIn: 300,
669
+ responseContentDisposition: `attachment; filename="${key.split("/").pop()}"`,
670
+ });
671
+ return c.redirect(url);
672
+ });
673
+
674
+ // JSON URL pattern — return URL for frontend to use
675
+ app.get("/api/file-url/:key", async (c) => {
676
+ const key = c.req.param("key");
677
+ const { url, expiresAt } = await storage.createDownloadUrl(key);
678
+ return c.json({ url, expiresAt: expiresAt.toISOString() });
679
+ });
680
+ ```
681
+
682
+ **Options (`CreateDownloadUrlOptions`):**
683
+ | Option | Type | Default | Description |
684
+ |--------|------|---------|-------------|
685
+ | `expiresIn` | `number` | `3600` | URL expiry in seconds |
686
+ | `responseContentDisposition` | `string` | — | Override Content-Disposition (e.g., force download) |
687
+
688
+ **Return type (`PresignedUrl`):**
689
+ | Field | Type | Description |
690
+ |-------|------|-------------|
691
+ | `url` | `string` | The presigned URL for browser use |
692
+ | `expiresAt` | `Date` | When the URL expires |
693
+
694
+ #### Important Notes
695
+
696
+ - **Content-Type is NOT signed** into upload URLs — the browser sends it at upload time. This avoids charset mismatch issues.
697
+ - **URLs expire** — default 1 hour, configurable via `expiresIn`. If the URL is near credential expiry, `expiresIn` is clamped to the remaining credential lifetime.
698
+ - **Max file size:** 5GB per upload (R2 single PUT limit).
699
+ - **CORS is configured automatically** on R2 buckets during deploy.
700
+
586
701
  ## WebAssembly (WASM)
587
702
 
588
703
  Fling supports importing WebAssembly modules in your backend code. This enables using libraries like `@resvg/resvg-wasm` for SVG rendering, image processing, and other compute-intensive tasks.