@stacksjs/storage 0.74.41 → 0.74.42

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.
@@ -0,0 +1,52 @@
1
+ /** The presentation category for a mime type. */
2
+ export declare function fileKind(mime: string): FileKind;
3
+ /**
4
+ * Whether a preview can be rendered from the bytes alone.
5
+ *
6
+ * `false` does not mean "no preview is possible" - it means one cannot be made
7
+ * here, without a transcoder, a renderer or a headless browser. A caller that
8
+ * has those can still produce one; the point is that this module does not
9
+ * pretend it did.
10
+ *
11
+ * SVG is excluded from previewable despite being an image: rendering
12
+ * attacker-supplied SVG is script execution, and the decision to do it anyway
13
+ * belongs to the caller who knows where the file came from.
14
+ */
15
+ export declare function isPreviewable(mime: string): boolean;
16
+ /** Resolve the mime type from bytes first, then the name. */
17
+ export declare function resolveMime(bytes: Uint8Array, name?: string): { mime: string, source: FileInfo['mimeSource'] };
18
+ /**
19
+ * Describe a file from its bytes.
20
+ *
21
+ * `name` is optional and used only for the extension fallback, so this works
22
+ * for an in-memory upload that has no path yet.
23
+ *
24
+ * Image metadata is read through `ts-images`, imported lazily: a caller asking
25
+ * about a PDF should not pay to load an image decoder. A decode failure is not
26
+ * an error - a truncated or malformed image is still a file with a size and a
27
+ * type, and returning that beats throwing on the one case a file manager most
28
+ * needs to display.
29
+ */
30
+ export declare function fileInfoFromBytes(bytes: Uint8Array, name?: string): Promise<FileInfo>;
31
+ /** Describe a file on disk. */
32
+ export declare function fileInfo(path: string): Promise<FileInfo>;
33
+ export declare interface FileInfo {
34
+ mime: string
35
+ mimeSource: 'magic-bytes' | 'extension' | 'fallback'
36
+ size: number
37
+ kind: FileKind
38
+ previewable: boolean
39
+ image?: ImageInfo
40
+ }
41
+ /** What `ts-images` reports about a decoded image. */
42
+ export declare interface ImageInfo {
43
+ width: number
44
+ height: number
45
+ format: string
46
+ channels?: number
47
+ bitDepth?: number
48
+ hasAlpha?: boolean
49
+ colorSpace?: string
50
+ }
51
+ /** What a UI branches on: how to present the file, not what encodes it. */
52
+ export type FileKind = 'image' | 'video' | 'audio' | 'pdf' | 'text' | 'archive' | 'font' | 'other';
@@ -0,0 +1 @@
1
+ import{extname}from"node:path";import{detectMimeFromMagicBytes}from"./mime-verify";const EXTENSION_MIME={".txt":"text/plain",".md":"text/markdown",".csv":"text/csv",".json":"application/json",".xml":"application/xml",".svg":"image/svg+xml",".html":"text/html",".css":"text/css",".js":"text/javascript",".ts":"text/typescript",".yaml":"application/yaml",".yml":"application/yaml"},KIND_BY_TYPE=[[/^image\//,"image"],[/^video\//,"video"],[/^audio\//,"audio"],[/^font\/|^application\/(?:x-)?font/,"font"],[/^application\/pdf$/,"pdf"],[/^application\/(?:zip|x-tar|gzip|x-7z|x-rar|x-bzip)/,"archive"],[/^text\/|^application\/(?:json|xml|yaml|javascript|typescript)/,"text"]];export function fileKind(mime){for(const[pattern,kind]of KIND_BY_TYPE)if(pattern.test(mime))return kind;return"other"}export function isPreviewable(mime){if(mime==="image/svg+xml")return!1;return fileKind(mime)==="image"||fileKind(mime)==="text"}export function resolveMime(bytes,name){const sniffed=detectMimeFromMagicBytes(bytes);if(sniffed)return{mime:sniffed,source:"magic-bytes"};if(name){const byExtension=EXTENSION_MIME[extname(name).toLowerCase()];if(byExtension)return{mime:byExtension,source:"extension"}}return{mime:"application/octet-stream",source:"fallback"}}export async function fileInfoFromBytes(bytes,name){const{mime,source}=resolveMime(bytes,name),kind=fileKind(mime),info={mime,mimeSource:source,size:bytes.byteLength,kind,previewable:isPreviewable(mime)};if(kind==="image"&&mime!=="image/svg+xml")try{const{getMetadata}=await import("ts-images");info.image=await getMetadata(bytes)}catch{}return info}export async function fileInfo(path){const file=Bun.file(path),bytes=new Uint8Array(await file.arrayBuffer());return fileInfoFromBytes(bytes,path)}
package/dist/index.d.ts CHANGED
@@ -8,6 +8,7 @@ export type { DiskName, KnownDisks } from './types/filesystem';
8
8
  export type { SignedTokenVerification } from './signed-url';
9
9
  export type { SignedUrlOptions } from './types';
10
10
  export type { ParsedDiskPath } from './path-sanitize';
11
+ export type { FileInfo, FileKind, ImageInfo } from './file-info';
11
12
  export type { MimeVerifyResult } from './mime-verify';
12
13
  export type { S3PresignedPostInput, S3PresignedPostResult } from './s3-presigned-post';
13
14
  export type {
@@ -54,6 +55,7 @@ export { parseDiskPath, PathSanitizeError, sanitizePresignedDir, sanitizePresign
54
55
  // MIME re-verification — server-side check that uploaded bytes match
55
56
  // the claimed content type. Run this after a presigned upload
56
57
  // completes since `Content-Type` on a presigned PUT is caller-attested.
58
+ export { fileInfo, fileInfoFromBytes, fileKind, isPreviewable, resolveMime } from './file-info';
57
59
  export { detectMimeFromMagicBytes, verifyUploadedMime } from './mime-verify';
58
60
  // S3 presigned-POST policy signer (stacksjs/stacks#1888 Phase B).
59
61
  // Exposed as a standalone for callers that have their own S3 client
package/dist/index.js CHANGED
@@ -1 +1 @@
1
- export*from"./copy";export*from"./delete";export*from"./files";export*from"./folders";export*from"./fs";export*from"./glob";export*from"./hash";export*from"./helpers";export* as storage from"./storage";export*from"./zip";export{ensureBucket,ensureConfiguredBuckets}from"./provision";export*from"./adapters";export*from"./types";export*from"./drivers";export{serveFile}from"./static-serve";export{Storage,StorageManager}from"./facade";export{UploadedFile,uploadedFile,uploadedFiles}from"./uploaded-file";export{azureDisk,backblazeDisk,configFromEnv,filebaseDisk,gcsDisk,hetznerDisk,localDisk,r2Disk,s3Disk}from"./types/filesystem";export{clearRevokedSignedStorageTokens,createSignedStorageToken,isSignedStorageTokenRevoked,revokeSignedStorageToken,verifySignedStorageToken}from"./signed-url";export{parseDiskPath,PathSanitizeError,sanitizePresignedDir,sanitizePresignedFilename}from"./path-sanitize";export{detectMimeFromMagicBytes,verifyUploadedMime}from"./mime-verify";export{signS3PresignedPost}from"./s3-presigned-post";
1
+ export*from"./copy";export*from"./delete";export*from"./files";export*from"./folders";export*from"./fs";export*from"./glob";export*from"./hash";export*from"./helpers";export* as storage from"./storage";export*from"./zip";export{ensureBucket,ensureConfiguredBuckets}from"./provision";export*from"./adapters";export*from"./types";export*from"./drivers";export{serveFile}from"./static-serve";export{Storage,StorageManager}from"./facade";export{UploadedFile,uploadedFile,uploadedFiles}from"./uploaded-file";export{azureDisk,backblazeDisk,configFromEnv,filebaseDisk,gcsDisk,hetznerDisk,localDisk,r2Disk,s3Disk}from"./types/filesystem";export{clearRevokedSignedStorageTokens,createSignedStorageToken,isSignedStorageTokenRevoked,revokeSignedStorageToken,verifySignedStorageToken}from"./signed-url";export{parseDiskPath,PathSanitizeError,sanitizePresignedDir,sanitizePresignedFilename}from"./path-sanitize";export{fileInfo,fileInfoFromBytes,fileKind,isPreviewable,resolveMime}from"./file-info";export{detectMimeFromMagicBytes,verifyUploadedMime}from"./mime-verify";export{signS3PresignedPost}from"./s3-presigned-post";
package/package.json CHANGED
@@ -2,7 +2,7 @@
2
2
  "name": "@stacksjs/storage",
3
3
  "type": "module",
4
4
  "sideEffects": false,
5
- "version": "0.74.41",
5
+ "version": "0.74.42",
6
6
  "description": "The Stacks file system.",
7
7
  "author": "Chris Breuer",
8
8
  "contributors": [
@@ -61,20 +61,20 @@
61
61
  "prepublishOnly": "bun run build"
62
62
  },
63
63
  "dependencies": {
64
- "@stacksjs/arrays": "0.74.41",
65
- "@stacksjs/cli": "0.74.41",
66
- "@stacksjs/config": "0.74.41",
67
- "@stacksjs/env": "0.74.41",
68
- "@stacksjs/error-handling": "0.74.41",
69
- "@stacksjs/logging": "0.74.41",
70
- "@stacksjs/path": "0.74.41",
71
- "@stacksjs/strings": "0.74.41",
64
+ "@stacksjs/arrays": "0.74.42",
65
+ "@stacksjs/cli": "0.74.42",
66
+ "@stacksjs/config": "0.74.42",
67
+ "@stacksjs/env": "0.74.42",
68
+ "@stacksjs/error-handling": "0.74.42",
69
+ "@stacksjs/logging": "0.74.42",
70
+ "@stacksjs/path": "0.74.42",
71
+ "@stacksjs/strings": "0.74.42",
72
72
  "@stacksjs/ts-cloud": "^0.16.0",
73
73
  "@stacksjs/ts-xml": "^0.1.0",
74
74
  "ts-images": "^0.2.11"
75
75
  },
76
76
  "devDependencies": {
77
- "@stacksjs/types": "0.74.41",
77
+ "@stacksjs/types": "0.74.42",
78
78
  "better-dx": "^0.2.24"
79
79
  }
80
80
  }