@pichaflow/sdk 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 James Sinkala (PichaFlow)
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1,76 @@
1
+ /**
2
+ * Optimizes an image file natively in the browser using HTML5 Canvas.
3
+ * It resizes the longest edge to maxDimension (maintaining aspect ratio)
4
+ * and converts the output to WebP to save bandwidth and prevent Edge Engine OOM crashes.
5
+ *
6
+ * @param file The original File object from the input.
7
+ * @param maxDimension The maximum allowed size for the longest edge.
8
+ * @param quality The WebP compression quality (0.0 to 1.0).
9
+ * @returns A Promise resolving to the optimized File (or the original if no optimization was needed).
10
+ */
11
+ declare function optimizeImageForUpload(file: File, maxDimension?: number, quality?: number): Promise<File>;
12
+
13
+ interface PichaFlowConfig {
14
+ apiKey: string;
15
+ baseUrl?: string;
16
+ uploadUrl?: string;
17
+ fetchUrl?: string;
18
+ tenantId?: string;
19
+ }
20
+ interface UploadOptions {
21
+ tags?: string[];
22
+ alt?: string;
23
+ directory?: string;
24
+ aiModeration?: boolean;
25
+ autoAltText?: boolean;
26
+ tenantId?: string;
27
+ onProgress?: (progress: number) => void;
28
+ }
29
+ interface UploadResponse {
30
+ success: boolean;
31
+ id: string;
32
+ url: string;
33
+ alt: string;
34
+ }
35
+ declare class PichaFlowClient {
36
+ private apiKey;
37
+ private baseUrl;
38
+ private uploadUrl;
39
+ private fetchUrl;
40
+ private tenantId?;
41
+ constructor(config: PichaFlowConfig);
42
+ /**
43
+ * Upload an image file to PichaFlow with advanced safety and AI features.
44
+ */
45
+ upload(file: File, options?: UploadOptions): Promise<UploadResponse>;
46
+ /**
47
+ * Securely upload directly via a signed URL (Best for large files/reliability)
48
+ */
49
+ secureUpload(file: File, options?: UploadOptions): Promise<UploadResponse>;
50
+ /**
51
+ * Delete a single asset programmatically.
52
+ */
53
+ deleteAsset(id: string): Promise<{
54
+ success: boolean;
55
+ deleted: number;
56
+ }>;
57
+ /**
58
+ * Bulk delete multiple assets (up to 100 per request).
59
+ */
60
+ bulkDeleteAssets(ids: string[]): Promise<{
61
+ success: boolean;
62
+ deleted: number;
63
+ }>;
64
+ /**
65
+ * Generate a CDN URL for an asset with transformation parameters or presets
66
+ */
67
+ getDeliveryUrl(path: string, options?: {
68
+ w?: number;
69
+ h?: number;
70
+ q?: number;
71
+ f?: string;
72
+ preset?: string;
73
+ }): string;
74
+ }
75
+
76
+ export { PichaFlowClient, type PichaFlowConfig, type UploadOptions, type UploadResponse, optimizeImageForUpload };
@@ -0,0 +1,76 @@
1
+ /**
2
+ * Optimizes an image file natively in the browser using HTML5 Canvas.
3
+ * It resizes the longest edge to maxDimension (maintaining aspect ratio)
4
+ * and converts the output to WebP to save bandwidth and prevent Edge Engine OOM crashes.
5
+ *
6
+ * @param file The original File object from the input.
7
+ * @param maxDimension The maximum allowed size for the longest edge.
8
+ * @param quality The WebP compression quality (0.0 to 1.0).
9
+ * @returns A Promise resolving to the optimized File (or the original if no optimization was needed).
10
+ */
11
+ declare function optimizeImageForUpload(file: File, maxDimension?: number, quality?: number): Promise<File>;
12
+
13
+ interface PichaFlowConfig {
14
+ apiKey: string;
15
+ baseUrl?: string;
16
+ uploadUrl?: string;
17
+ fetchUrl?: string;
18
+ tenantId?: string;
19
+ }
20
+ interface UploadOptions {
21
+ tags?: string[];
22
+ alt?: string;
23
+ directory?: string;
24
+ aiModeration?: boolean;
25
+ autoAltText?: boolean;
26
+ tenantId?: string;
27
+ onProgress?: (progress: number) => void;
28
+ }
29
+ interface UploadResponse {
30
+ success: boolean;
31
+ id: string;
32
+ url: string;
33
+ alt: string;
34
+ }
35
+ declare class PichaFlowClient {
36
+ private apiKey;
37
+ private baseUrl;
38
+ private uploadUrl;
39
+ private fetchUrl;
40
+ private tenantId?;
41
+ constructor(config: PichaFlowConfig);
42
+ /**
43
+ * Upload an image file to PichaFlow with advanced safety and AI features.
44
+ */
45
+ upload(file: File, options?: UploadOptions): Promise<UploadResponse>;
46
+ /**
47
+ * Securely upload directly via a signed URL (Best for large files/reliability)
48
+ */
49
+ secureUpload(file: File, options?: UploadOptions): Promise<UploadResponse>;
50
+ /**
51
+ * Delete a single asset programmatically.
52
+ */
53
+ deleteAsset(id: string): Promise<{
54
+ success: boolean;
55
+ deleted: number;
56
+ }>;
57
+ /**
58
+ * Bulk delete multiple assets (up to 100 per request).
59
+ */
60
+ bulkDeleteAssets(ids: string[]): Promise<{
61
+ success: boolean;
62
+ deleted: number;
63
+ }>;
64
+ /**
65
+ * Generate a CDN URL for an asset with transformation parameters or presets
66
+ */
67
+ getDeliveryUrl(path: string, options?: {
68
+ w?: number;
69
+ h?: number;
70
+ q?: number;
71
+ f?: string;
72
+ preset?: string;
73
+ }): string;
74
+ }
75
+
76
+ export { PichaFlowClient, type PichaFlowConfig, type UploadOptions, type UploadResponse, optimizeImageForUpload };
package/dist/index.js ADDED
@@ -0,0 +1,2 @@
1
+ var T=Object.create;var f=Object.defineProperty;var x=Object.getOwnPropertyDescriptor;var S=Object.getOwnPropertyNames;var $=Object.getPrototypeOf,M=Object.prototype.hasOwnProperty;var F=(s,r)=>{for(var e in r)f(s,e,{get:r[e],enumerable:!0})},b=(s,r,e,t)=>{if(r&&typeof r=="object"||typeof r=="function")for(let a of S(r))!M.call(s,a)&&a!==e&&f(s,a,{get:()=>r[a],enumerable:!(t=x(r,a))||t.enumerable});return s};var O=(s,r,e)=>(e=s!=null?T($(s)):{},b(r||!s||!s.__esModule?f(e,"default",{value:s,enumerable:!0}):e,s)),A=s=>b(f({},"__esModule",{value:!0}),s);var I={};F(I,{PichaFlowClient:()=>w,optimizeImageForUpload:()=>E});module.exports=A(I);async function E(s,r=2048,e=.85){if(!s.type.startsWith("image/"))return s;let t=s;if(s.type==="image/heic"||s.type==="image/heif"||s.name.toLowerCase().endsWith(".heic")||s.name.toLowerCase().endsWith(".heif"))try{let i=(await import("heic2any")).default,c=await i({blob:s,toType:"image/jpeg",quality:.9});t=new File([c],s.name.replace(/\.[^/.]+$/,".jpg"),{type:"image/jpeg",lastModified:Date.now()})}catch(i){console.warn("HEIC conversion failed or heic2any unavailable. Falling back to original file.",i)}return new Promise((i,c)=>{let o=new FileReader;o.onerror=c,o.onload=n=>{var u;let p=new Image;p.onerror=()=>{i(t)},p.onload=()=>{let d=p.width,l=p.height,g=!1;(d>r||l>r)&&(g=!0,d>l?(l=Math.round(l/d*r),d=r):(d=Math.round(d/l*r),l=r));let h=document.createElement("canvas");h.width=d,h.height=l;let m=h.getContext("2d");if(!m)return i(t);m.drawImage(p,0,0,d,l),h.toBlob(y=>{if(!y)return i(t);let P=t.name.replace(/\.[^/.]+$/,"")+".webp",U=new File([y],P,{type:"image/webp",lastModified:Date.now()});U.size>t.size&&!g?i(t):i(U)},"image/webp",e)},p.src=(u=n.target)==null?void 0:u.result},o.readAsDataURL(t)})}var w=class{apiKey;baseUrl;uploadUrl;fetchUrl;tenantId;constructor(r){this.apiKey=r.apiKey,this.baseUrl=r.baseUrl||"https://api.pichaflow.com",this.uploadUrl=r.uploadUrl||"https://egn.pichaflow.com",this.fetchUrl=r.fetchUrl||"https://cdn.pichaflow.com",this.tenantId=r.tenantId}async upload(r,e={}){let t=new FormData;t.append("file",r),e.tags&&t.append("tags",JSON.stringify(e.tags)),e.alt&&t.append("alt",e.alt),e.directory&&t.append("directory",e.directory),e.aiModeration!==void 0&&t.append("aiModeration",String(e.aiModeration)),e.autoAltText!==void 0&&t.append("autoAltText",String(e.autoAltText));let a=new XMLHttpRequest,i=`${this.uploadUrl}/v1/upload`;return new Promise((c,o)=>{a.open("POST",i),a.setRequestHeader("Authorization",`Bearer ${this.apiKey}`),a.upload.onprogress=n=>{if(n.lengthComputable&&e.onProgress){let p=Math.round(n.loaded/n.total*100);e.onProgress(p)}},a.onload=()=>{if(a.status>=200&&a.status<300)c(JSON.parse(a.responseText));else try{let n=JSON.parse(a.responseText);o(new Error(n.message||`Upload failed with status ${a.status}`))}catch{o(new Error(`Upload failed with status ${a.status}`))}},a.onerror=()=>o(new Error("Network error during upload")),a.send(t)})}async secureUpload(r,e={}){let t=await fetch(`${this.baseUrl}/v1/upload/sign`,{method:"POST",headers:{Authorization:`Bearer ${this.apiKey}`,"Content-Type":"application/json"},body:JSON.stringify({tenantId:e.tenantId||this.tenantId})});if(!t.ok)throw new Error("Failed to generate secure upload signature");let{signature:a,timestamp:i,tenantId:c}=await t.json(),o=new FormData;o.append("file",r),o.append("tenantId",c),e.tags&&o.append("tags",JSON.stringify(e.tags)),e.alt&&o.append("alt",e.alt),e.directory&&o.append("directory",e.directory),e.aiModeration!==void 0&&o.append("aiModeration",String(e.aiModeration)),e.autoAltText!==void 0&&o.append("autoAltText",String(e.autoAltText));let n=new XMLHttpRequest,p=`${this.uploadUrl}/v1/upload`;return new Promise((u,d)=>{n.open("POST",p),n.setRequestHeader("X-Picha-Signature",a),n.setRequestHeader("X-Picha-Timestamp",String(i)),n.upload.onprogress=l=>{if(l.lengthComputable&&e.onProgress){let g=Math.round(l.loaded/l.total*100);e.onProgress(g)}},n.onload=()=>{if(n.status>=200&&n.status<300)u(JSON.parse(n.responseText));else try{let l=JSON.parse(n.responseText);d(new Error(l.message||`Upload failed with status ${n.status}`))}catch{d(new Error(n.responseText||`Upload failed with status ${n.status}`))}},n.onerror=()=>d(new Error("Network error during upload")),n.send(o)})}async deleteAsset(r){let e=`${this.uploadUrl}/v1/assets/${r}`,t=await fetch(e,{method:"DELETE",headers:{Authorization:`Bearer ${this.apiKey}`}});if(!t.ok){let a=`Deletion failed with status ${t.status}`;try{a=(await t.json()).message||a}catch{a=await t.text()||a}throw new Error(a)}return await t.json()}async bulkDeleteAssets(r){let e=`${this.uploadUrl}/v1/assets/delete`,t=await fetch(e,{method:"POST",headers:{Authorization:`Bearer ${this.apiKey}`,"Content-Type":"application/json"},body:JSON.stringify({ids:r})});if(!t.ok){let a=`Bulk deletion failed with status ${t.status}`;try{a=(await t.json()).message||a}catch{a=await t.text()||a}throw new Error(a)}return await t.json()}getDeliveryUrl(r,e={}){let t=new URL(`${this.fetchUrl}/${r}`);return e.preset&&t.searchParams.set("preset",e.preset),e.w&&t.searchParams.set("w",e.w.toString()),e.h&&t.searchParams.set("h",e.h.toString()),e.q&&t.searchParams.set("q",e.q.toString()),e.f&&t.searchParams.set("f",e.f),t.toString()}};0&&(module.exports={PichaFlowClient,optimizeImageForUpload});
2
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/index.ts","../src/client-optimizer.ts"],"sourcesContent":["export interface PichaFlowConfig {\n apiKey: string;\n baseUrl?: string; // Dashboard/Management API (e.g. api.pichaflow.com)\n uploadUrl?: string; // Edge Upload Engine (e.g. egn.pichaflow.com)\n fetchUrl?: string; // Edge Fetch/CDN Engine (e.g. cdn.pichaflow.com)\n tenantId?: string; // Optional: Required when using internal session auth instead of API keys\n}\n\nexport interface UploadOptions {\n tags?: string[];\n alt?: string;\n directory?: string; // The folder to place the asset in\n aiModeration?: boolean; // Enable NSFW & Brand Safety checks\n autoAltText?: boolean; // Automatically generate AI alt-text\n tenantId?: string; // Override instance tenantId\n onProgress?: (progress: number) => void;\n}\n\nexport interface UploadResponse {\n success: boolean;\n id: string;\n url: string;\n alt: string;\n}\n\nexport class PichaFlowClient {\n private apiKey: string;\n private baseUrl: string;\n private uploadUrl: string;\n private fetchUrl: string;\n private tenantId?: string;\n\n constructor(config: PichaFlowConfig) {\n this.apiKey = config.apiKey;\n this.baseUrl = config.baseUrl || 'https://api.pichaflow.com';\n this.uploadUrl = config.uploadUrl || 'https://egn.pichaflow.com';\n this.fetchUrl = config.fetchUrl || 'https://cdn.pichaflow.com';\n this.tenantId = config.tenantId;\n }\n\n /**\n * Upload an image file to PichaFlow with advanced safety and AI features.\n */\n async upload(file: File, options: UploadOptions = {}): Promise<UploadResponse> {\n const formData = new FormData();\n formData.append('file', file);\n\n if (options.tags) formData.append('tags', JSON.stringify(options.tags));\n if (options.alt) formData.append('alt', options.alt);\n if (options.directory) formData.append('directory', options.directory);\n if (options.aiModeration !== undefined) formData.append('aiModeration', String(options.aiModeration));\n if (options.autoAltText !== undefined) formData.append('autoAltText', String(options.autoAltText));\n\n const xhr = new XMLHttpRequest();\n const url = `${this.uploadUrl}/v1/upload`;\n\n return new Promise((resolve, reject) => {\n xhr.open('POST', url);\n xhr.setRequestHeader('Authorization', `Bearer ${this.apiKey}`);\n\n xhr.upload.onprogress = (event) => {\n if (event.lengthComputable && options.onProgress) {\n const progress = Math.round((event.loaded / event.total) * 100);\n options.onProgress(progress);\n }\n };\n\n xhr.onload = () => {\n if (xhr.status >= 200 && xhr.status < 300) {\n resolve(JSON.parse(xhr.responseText));\n } else {\n try {\n const error = JSON.parse(xhr.responseText);\n reject(new Error(error.message || `Upload failed with status ${xhr.status}`));\n } catch {\n reject(new Error(`Upload failed with status ${xhr.status}`));\n }\n }\n };\n\n xhr.onerror = () => reject(new Error('Network error during upload'));\n xhr.send(formData);\n });\n }\n\n /**\n * Securely upload directly via a signed URL (Best for large files/reliability)\n */\n async secureUpload(file: File, options: UploadOptions = {}): Promise<UploadResponse> {\n // 1. Get signed upload context from the Management API\n const signResponse = await fetch(`${this.baseUrl}/v1/upload/sign`, {\n method: 'POST',\n headers: {\n 'Authorization': `Bearer ${this.apiKey}`,\n 'Content-Type': 'application/json'\n },\n body: JSON.stringify({ tenantId: options.tenantId || this.tenantId })\n });\n\n if (!signResponse.ok) {\n throw new Error('Failed to generate secure upload signature');\n }\n\n const { signature, timestamp, tenantId } = await signResponse.json();\n\n // 2. Perform the actual upload to the Edge Engine\n const formData = new FormData();\n formData.append('file', file);\n formData.append('tenantId', tenantId);\n if (options.tags) formData.append('tags', JSON.stringify(options.tags));\n if (options.alt) formData.append('alt', options.alt);\n if (options.directory) formData.append('directory', options.directory);\n if (options.aiModeration !== undefined) formData.append('aiModeration', String(options.aiModeration));\n if (options.autoAltText !== undefined) formData.append('autoAltText', String(options.autoAltText));\n\n const xhr = new XMLHttpRequest();\n const url = `${this.uploadUrl}/v1/upload`;\n\n return new Promise((resolve, reject) => {\n xhr.open('POST', url);\n xhr.setRequestHeader('X-Picha-Signature', signature);\n xhr.setRequestHeader('X-Picha-Timestamp', String(timestamp));\n\n xhr.upload.onprogress = (event) => {\n if (event.lengthComputable && options.onProgress) {\n const progress = Math.round((event.loaded / event.total) * 100);\n options.onProgress(progress);\n }\n };\n\n xhr.onload = () => {\n if (xhr.status >= 200 && xhr.status < 300) {\n resolve(JSON.parse(xhr.responseText));\n } else {\n try {\n const error = JSON.parse(xhr.responseText);\n reject(new Error(error.message || `Upload failed with status ${xhr.status}`));\n } catch {\n reject(new Error(xhr.responseText || `Upload failed with status ${xhr.status}`));\n }\n }\n };\n\n xhr.onerror = () => reject(new Error('Network error during upload'));\n xhr.send(formData);\n });\n }\n\n /**\n * Delete a single asset programmatically.\n */\n async deleteAsset(id: string): Promise<{ success: boolean; deleted: number }> {\n const url = `${this.uploadUrl}/v1/assets/${id}`;\n const response = await fetch(url, {\n method: 'DELETE',\n headers: {\n 'Authorization': `Bearer ${this.apiKey}`\n }\n });\n\n if (!response.ok) {\n let errorMsg = `Deletion failed with status ${response.status}`;\n try {\n const errJson = await response.json();\n errorMsg = errJson.message || errorMsg;\n } catch {\n const errText = await response.text();\n errorMsg = errText || errorMsg;\n }\n throw new Error(errorMsg);\n }\n\n return await response.json();\n }\n\n /**\n * Bulk delete multiple assets (up to 100 per request).\n */\n async bulkDeleteAssets(ids: string[]): Promise<{ success: boolean; deleted: number }> {\n const url = `${this.uploadUrl}/v1/assets/delete`;\n const response = await fetch(url, {\n method: 'POST',\n headers: {\n 'Authorization': `Bearer ${this.apiKey}`,\n 'Content-Type': 'application/json'\n },\n body: JSON.stringify({ ids })\n });\n\n if (!response.ok) {\n let errorMsg = `Bulk deletion failed with status ${response.status}`;\n try {\n const errJson = await response.json();\n errorMsg = errJson.message || errorMsg;\n } catch {\n const errText = await response.text();\n errorMsg = errText || errorMsg;\n }\n throw new Error(errorMsg);\n }\n\n return await response.json();\n }\n\n /**\n * Generate a CDN URL for an asset with transformation parameters or presets\n */\n getDeliveryUrl(path: string, options: { w?: number; h?: number; q?: number; f?: string, preset?: string } = {}): string {\n const url = new URL(`${this.fetchUrl}/${path}`);\n if (options.preset) url.searchParams.set('preset', options.preset);\n if (options.w) url.searchParams.set('w', options.w.toString());\n if (options.h) url.searchParams.set('h', options.h.toString());\n if (options.q) url.searchParams.set('q', options.q.toString());\n if (options.f) url.searchParams.set('f', options.f);\n return url.toString();\n }\n}\n\nexport * from './client-optimizer.js';\n","/**\n * Optimizes an image file natively in the browser using HTML5 Canvas.\n * It resizes the longest edge to maxDimension (maintaining aspect ratio) \n * and converts the output to WebP to save bandwidth and prevent Edge Engine OOM crashes.\n *\n * @param file The original File object from the input.\n * @param maxDimension The maximum allowed size for the longest edge.\n * @param quality The WebP compression quality (0.0 to 1.0).\n * @returns A Promise resolving to the optimized File (or the original if no optimization was needed).\n */\nexport async function optimizeImageForUpload(file: File, maxDimension = 2048, quality = 0.85): Promise<File> {\n // Only process images\n if (!file.type.startsWith('image/')) {\n return file;\n }\n\n let processableFile = file;\n\n // Handle HEIC/HEIF files which browsers cannot natively decode\n const isHeic = file.type === 'image/heic' || file.type === 'image/heif' || file.name.toLowerCase().endsWith('.heic') || file.name.toLowerCase().endsWith('.heif');\n \n if (isHeic) {\n try {\n // Dynamically import to prevent bloating the main SDK bundle for non-iOS users\n // @ts-ignore\n const heic2any = (await import('heic2any')).default;\n const convertedBlob = await heic2any({\n blob: file,\n toType: 'image/jpeg',\n quality: 0.9 // High quality pre-conversion\n }) as Blob;\n \n processableFile = new File([convertedBlob], file.name.replace(/\\.[^/.]+$/, \".jpg\"), {\n type: 'image/jpeg',\n lastModified: Date.now(),\n });\n } catch (err) {\n console.warn(\"HEIC conversion failed or heic2any unavailable. Falling back to original file.\", err);\n // We will still pass it through, but the Image() loader will fail and return the raw file.\n }\n }\n\n return new Promise((resolve, reject) => {\n const reader = new FileReader();\n reader.onerror = reject;\n reader.onload = (e) => {\n const img = new Image();\n img.onerror = () => {\n // Browser cannot natively decode this format (e.g., weird TIFFs or failed HEIC).\n // Fallback to returning the best available file and let the backend handle it.\n resolve(processableFile);\n };\n img.onload = () => {\n let width = img.width;\n let height = img.height;\n\n // If the image is smaller than maxDimension, just return it as WebP\n let shouldResize = false;\n if (width > maxDimension || height > maxDimension) {\n shouldResize = true;\n if (width > height) {\n height = Math.round((height / width) * maxDimension);\n width = maxDimension;\n } else {\n width = Math.round((width / height) * maxDimension);\n height = maxDimension;\n }\n }\n\n const canvas = document.createElement('canvas');\n canvas.width = width;\n canvas.height = height;\n\n const ctx = canvas.getContext('2d');\n if (!ctx) {\n return resolve(processableFile); // Fallback if canvas fails\n }\n\n // Draw original image on canvas with new dimensions\n ctx.drawImage(img, 0, 0, width, height);\n\n canvas.toBlob(\n (blob) => {\n if (!blob) return resolve(processableFile);\n\n // Strip old extension and append .webp\n const originalName = processableFile.name;\n const newName = originalName.replace(/\\.[^/.]+$/, \"\") + \".webp\";\n\n const optimizedFile = new File([blob], newName, {\n type: 'image/webp',\n lastModified: Date.now(),\n });\n\n // If the optimization somehow bloated the file (unlikely for JPEGs to WebP, but possible for tiny SVGs/PNGs), fallback\n if (optimizedFile.size > processableFile.size && !shouldResize) {\n resolve(processableFile);\n } else {\n resolve(optimizedFile);\n }\n },\n 'image/webp',\n quality\n );\n };\n img.src = e.target?.result as string;\n };\n reader.readAsDataURL(processableFile);\n });\n}\n"],"mappings":"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","names":["index_exports","__export","PichaFlowClient","optimizeImageForUpload","__toCommonJS","optimizeImageForUpload","file","maxDimension","quality","processableFile","heic2any","convertedBlob","err","resolve","reject","reader","e","_a","img","width","height","shouldResize","canvas","ctx","blob","newName","optimizedFile","PichaFlowClient","config","file","options","formData","xhr","url","resolve","reject","event","progress","error","signResponse","signature","timestamp","tenantId","id","response","errorMsg","ids","path"]}
package/dist/index.mjs ADDED
@@ -0,0 +1,2 @@
1
+ async function P(d,a=2048,e=.85){if(!d.type.startsWith("image/"))return d;let t=d;if(d.type==="image/heic"||d.type==="image/heif"||d.name.toLowerCase().endsWith(".heic")||d.name.toLowerCase().endsWith(".heif"))try{let n=(await import("heic2any")).default,c=await n({blob:d,toType:"image/jpeg",quality:.9});t=new File([c],d.name.replace(/\.[^/.]+$/,".jpg"),{type:"image/jpeg",lastModified:Date.now()})}catch(n){console.warn("HEIC conversion failed or heic2any unavailable. Falling back to original file.",n)}return new Promise((n,c)=>{let i=new FileReader;i.onerror=c,i.onload=s=>{var u;let p=new Image;p.onerror=()=>{n(t)},p.onload=()=>{let l=p.width,o=p.height,g=!1;(l>a||o>a)&&(g=!0,l>o?(o=Math.round(o/l*a),l=a):(l=Math.round(l/o*a),o=a));let h=document.createElement("canvas");h.width=l,h.height=o;let f=h.getContext("2d");if(!f)return n(t);f.drawImage(p,0,0,l,o),h.toBlob(w=>{if(!w)return n(t);let U=t.name.replace(/\.[^/.]+$/,"")+".webp",m=new File([w],U,{type:"image/webp",lastModified:Date.now()});m.size>t.size&&!g?n(t):n(m)},"image/webp",e)},p.src=(u=s.target)==null?void 0:u.result},i.readAsDataURL(t)})}var y=class{apiKey;baseUrl;uploadUrl;fetchUrl;tenantId;constructor(a){this.apiKey=a.apiKey,this.baseUrl=a.baseUrl||"https://api.pichaflow.com",this.uploadUrl=a.uploadUrl||"https://egn.pichaflow.com",this.fetchUrl=a.fetchUrl||"https://cdn.pichaflow.com",this.tenantId=a.tenantId}async upload(a,e={}){let t=new FormData;t.append("file",a),e.tags&&t.append("tags",JSON.stringify(e.tags)),e.alt&&t.append("alt",e.alt),e.directory&&t.append("directory",e.directory),e.aiModeration!==void 0&&t.append("aiModeration",String(e.aiModeration)),e.autoAltText!==void 0&&t.append("autoAltText",String(e.autoAltText));let r=new XMLHttpRequest,n=`${this.uploadUrl}/v1/upload`;return new Promise((c,i)=>{r.open("POST",n),r.setRequestHeader("Authorization",`Bearer ${this.apiKey}`),r.upload.onprogress=s=>{if(s.lengthComputable&&e.onProgress){let p=Math.round(s.loaded/s.total*100);e.onProgress(p)}},r.onload=()=>{if(r.status>=200&&r.status<300)c(JSON.parse(r.responseText));else try{let s=JSON.parse(r.responseText);i(new Error(s.message||`Upload failed with status ${r.status}`))}catch{i(new Error(`Upload failed with status ${r.status}`))}},r.onerror=()=>i(new Error("Network error during upload")),r.send(t)})}async secureUpload(a,e={}){let t=await fetch(`${this.baseUrl}/v1/upload/sign`,{method:"POST",headers:{Authorization:`Bearer ${this.apiKey}`,"Content-Type":"application/json"},body:JSON.stringify({tenantId:e.tenantId||this.tenantId})});if(!t.ok)throw new Error("Failed to generate secure upload signature");let{signature:r,timestamp:n,tenantId:c}=await t.json(),i=new FormData;i.append("file",a),i.append("tenantId",c),e.tags&&i.append("tags",JSON.stringify(e.tags)),e.alt&&i.append("alt",e.alt),e.directory&&i.append("directory",e.directory),e.aiModeration!==void 0&&i.append("aiModeration",String(e.aiModeration)),e.autoAltText!==void 0&&i.append("autoAltText",String(e.autoAltText));let s=new XMLHttpRequest,p=`${this.uploadUrl}/v1/upload`;return new Promise((u,l)=>{s.open("POST",p),s.setRequestHeader("X-Picha-Signature",r),s.setRequestHeader("X-Picha-Timestamp",String(n)),s.upload.onprogress=o=>{if(o.lengthComputable&&e.onProgress){let g=Math.round(o.loaded/o.total*100);e.onProgress(g)}},s.onload=()=>{if(s.status>=200&&s.status<300)u(JSON.parse(s.responseText));else try{let o=JSON.parse(s.responseText);l(new Error(o.message||`Upload failed with status ${s.status}`))}catch{l(new Error(s.responseText||`Upload failed with status ${s.status}`))}},s.onerror=()=>l(new Error("Network error during upload")),s.send(i)})}async deleteAsset(a){let e=`${this.uploadUrl}/v1/assets/${a}`,t=await fetch(e,{method:"DELETE",headers:{Authorization:`Bearer ${this.apiKey}`}});if(!t.ok){let r=`Deletion failed with status ${t.status}`;try{r=(await t.json()).message||r}catch{r=await t.text()||r}throw new Error(r)}return await t.json()}async bulkDeleteAssets(a){let e=`${this.uploadUrl}/v1/assets/delete`,t=await fetch(e,{method:"POST",headers:{Authorization:`Bearer ${this.apiKey}`,"Content-Type":"application/json"},body:JSON.stringify({ids:a})});if(!t.ok){let r=`Bulk deletion failed with status ${t.status}`;try{r=(await t.json()).message||r}catch{r=await t.text()||r}throw new Error(r)}return await t.json()}getDeliveryUrl(a,e={}){let t=new URL(`${this.fetchUrl}/${a}`);return e.preset&&t.searchParams.set("preset",e.preset),e.w&&t.searchParams.set("w",e.w.toString()),e.h&&t.searchParams.set("h",e.h.toString()),e.q&&t.searchParams.set("q",e.q.toString()),e.f&&t.searchParams.set("f",e.f),t.toString()}};export{y as PichaFlowClient,P as optimizeImageForUpload};
2
+ //# sourceMappingURL=index.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/client-optimizer.ts","../src/index.ts"],"sourcesContent":["/**\n * Optimizes an image file natively in the browser using HTML5 Canvas.\n * It resizes the longest edge to maxDimension (maintaining aspect ratio) \n * and converts the output to WebP to save bandwidth and prevent Edge Engine OOM crashes.\n *\n * @param file The original File object from the input.\n * @param maxDimension The maximum allowed size for the longest edge.\n * @param quality The WebP compression quality (0.0 to 1.0).\n * @returns A Promise resolving to the optimized File (or the original if no optimization was needed).\n */\nexport async function optimizeImageForUpload(file: File, maxDimension = 2048, quality = 0.85): Promise<File> {\n // Only process images\n if (!file.type.startsWith('image/')) {\n return file;\n }\n\n let processableFile = file;\n\n // Handle HEIC/HEIF files which browsers cannot natively decode\n const isHeic = file.type === 'image/heic' || file.type === 'image/heif' || file.name.toLowerCase().endsWith('.heic') || file.name.toLowerCase().endsWith('.heif');\n \n if (isHeic) {\n try {\n // Dynamically import to prevent bloating the main SDK bundle for non-iOS users\n // @ts-ignore\n const heic2any = (await import('heic2any')).default;\n const convertedBlob = await heic2any({\n blob: file,\n toType: 'image/jpeg',\n quality: 0.9 // High quality pre-conversion\n }) as Blob;\n \n processableFile = new File([convertedBlob], file.name.replace(/\\.[^/.]+$/, \".jpg\"), {\n type: 'image/jpeg',\n lastModified: Date.now(),\n });\n } catch (err) {\n console.warn(\"HEIC conversion failed or heic2any unavailable. Falling back to original file.\", err);\n // We will still pass it through, but the Image() loader will fail and return the raw file.\n }\n }\n\n return new Promise((resolve, reject) => {\n const reader = new FileReader();\n reader.onerror = reject;\n reader.onload = (e) => {\n const img = new Image();\n img.onerror = () => {\n // Browser cannot natively decode this format (e.g., weird TIFFs or failed HEIC).\n // Fallback to returning the best available file and let the backend handle it.\n resolve(processableFile);\n };\n img.onload = () => {\n let width = img.width;\n let height = img.height;\n\n // If the image is smaller than maxDimension, just return it as WebP\n let shouldResize = false;\n if (width > maxDimension || height > maxDimension) {\n shouldResize = true;\n if (width > height) {\n height = Math.round((height / width) * maxDimension);\n width = maxDimension;\n } else {\n width = Math.round((width / height) * maxDimension);\n height = maxDimension;\n }\n }\n\n const canvas = document.createElement('canvas');\n canvas.width = width;\n canvas.height = height;\n\n const ctx = canvas.getContext('2d');\n if (!ctx) {\n return resolve(processableFile); // Fallback if canvas fails\n }\n\n // Draw original image on canvas with new dimensions\n ctx.drawImage(img, 0, 0, width, height);\n\n canvas.toBlob(\n (blob) => {\n if (!blob) return resolve(processableFile);\n\n // Strip old extension and append .webp\n const originalName = processableFile.name;\n const newName = originalName.replace(/\\.[^/.]+$/, \"\") + \".webp\";\n\n const optimizedFile = new File([blob], newName, {\n type: 'image/webp',\n lastModified: Date.now(),\n });\n\n // If the optimization somehow bloated the file (unlikely for JPEGs to WebP, but possible for tiny SVGs/PNGs), fallback\n if (optimizedFile.size > processableFile.size && !shouldResize) {\n resolve(processableFile);\n } else {\n resolve(optimizedFile);\n }\n },\n 'image/webp',\n quality\n );\n };\n img.src = e.target?.result as string;\n };\n reader.readAsDataURL(processableFile);\n });\n}\n","export interface PichaFlowConfig {\n apiKey: string;\n baseUrl?: string; // Dashboard/Management API (e.g. api.pichaflow.com)\n uploadUrl?: string; // Edge Upload Engine (e.g. egn.pichaflow.com)\n fetchUrl?: string; // Edge Fetch/CDN Engine (e.g. cdn.pichaflow.com)\n tenantId?: string; // Optional: Required when using internal session auth instead of API keys\n}\n\nexport interface UploadOptions {\n tags?: string[];\n alt?: string;\n directory?: string; // The folder to place the asset in\n aiModeration?: boolean; // Enable NSFW & Brand Safety checks\n autoAltText?: boolean; // Automatically generate AI alt-text\n tenantId?: string; // Override instance tenantId\n onProgress?: (progress: number) => void;\n}\n\nexport interface UploadResponse {\n success: boolean;\n id: string;\n url: string;\n alt: string;\n}\n\nexport class PichaFlowClient {\n private apiKey: string;\n private baseUrl: string;\n private uploadUrl: string;\n private fetchUrl: string;\n private tenantId?: string;\n\n constructor(config: PichaFlowConfig) {\n this.apiKey = config.apiKey;\n this.baseUrl = config.baseUrl || 'https://api.pichaflow.com';\n this.uploadUrl = config.uploadUrl || 'https://egn.pichaflow.com';\n this.fetchUrl = config.fetchUrl || 'https://cdn.pichaflow.com';\n this.tenantId = config.tenantId;\n }\n\n /**\n * Upload an image file to PichaFlow with advanced safety and AI features.\n */\n async upload(file: File, options: UploadOptions = {}): Promise<UploadResponse> {\n const formData = new FormData();\n formData.append('file', file);\n\n if (options.tags) formData.append('tags', JSON.stringify(options.tags));\n if (options.alt) formData.append('alt', options.alt);\n if (options.directory) formData.append('directory', options.directory);\n if (options.aiModeration !== undefined) formData.append('aiModeration', String(options.aiModeration));\n if (options.autoAltText !== undefined) formData.append('autoAltText', String(options.autoAltText));\n\n const xhr = new XMLHttpRequest();\n const url = `${this.uploadUrl}/v1/upload`;\n\n return new Promise((resolve, reject) => {\n xhr.open('POST', url);\n xhr.setRequestHeader('Authorization', `Bearer ${this.apiKey}`);\n\n xhr.upload.onprogress = (event) => {\n if (event.lengthComputable && options.onProgress) {\n const progress = Math.round((event.loaded / event.total) * 100);\n options.onProgress(progress);\n }\n };\n\n xhr.onload = () => {\n if (xhr.status >= 200 && xhr.status < 300) {\n resolve(JSON.parse(xhr.responseText));\n } else {\n try {\n const error = JSON.parse(xhr.responseText);\n reject(new Error(error.message || `Upload failed with status ${xhr.status}`));\n } catch {\n reject(new Error(`Upload failed with status ${xhr.status}`));\n }\n }\n };\n\n xhr.onerror = () => reject(new Error('Network error during upload'));\n xhr.send(formData);\n });\n }\n\n /**\n * Securely upload directly via a signed URL (Best for large files/reliability)\n */\n async secureUpload(file: File, options: UploadOptions = {}): Promise<UploadResponse> {\n // 1. Get signed upload context from the Management API\n const signResponse = await fetch(`${this.baseUrl}/v1/upload/sign`, {\n method: 'POST',\n headers: {\n 'Authorization': `Bearer ${this.apiKey}`,\n 'Content-Type': 'application/json'\n },\n body: JSON.stringify({ tenantId: options.tenantId || this.tenantId })\n });\n\n if (!signResponse.ok) {\n throw new Error('Failed to generate secure upload signature');\n }\n\n const { signature, timestamp, tenantId } = await signResponse.json();\n\n // 2. Perform the actual upload to the Edge Engine\n const formData = new FormData();\n formData.append('file', file);\n formData.append('tenantId', tenantId);\n if (options.tags) formData.append('tags', JSON.stringify(options.tags));\n if (options.alt) formData.append('alt', options.alt);\n if (options.directory) formData.append('directory', options.directory);\n if (options.aiModeration !== undefined) formData.append('aiModeration', String(options.aiModeration));\n if (options.autoAltText !== undefined) formData.append('autoAltText', String(options.autoAltText));\n\n const xhr = new XMLHttpRequest();\n const url = `${this.uploadUrl}/v1/upload`;\n\n return new Promise((resolve, reject) => {\n xhr.open('POST', url);\n xhr.setRequestHeader('X-Picha-Signature', signature);\n xhr.setRequestHeader('X-Picha-Timestamp', String(timestamp));\n\n xhr.upload.onprogress = (event) => {\n if (event.lengthComputable && options.onProgress) {\n const progress = Math.round((event.loaded / event.total) * 100);\n options.onProgress(progress);\n }\n };\n\n xhr.onload = () => {\n if (xhr.status >= 200 && xhr.status < 300) {\n resolve(JSON.parse(xhr.responseText));\n } else {\n try {\n const error = JSON.parse(xhr.responseText);\n reject(new Error(error.message || `Upload failed with status ${xhr.status}`));\n } catch {\n reject(new Error(xhr.responseText || `Upload failed with status ${xhr.status}`));\n }\n }\n };\n\n xhr.onerror = () => reject(new Error('Network error during upload'));\n xhr.send(formData);\n });\n }\n\n /**\n * Delete a single asset programmatically.\n */\n async deleteAsset(id: string): Promise<{ success: boolean; deleted: number }> {\n const url = `${this.uploadUrl}/v1/assets/${id}`;\n const response = await fetch(url, {\n method: 'DELETE',\n headers: {\n 'Authorization': `Bearer ${this.apiKey}`\n }\n });\n\n if (!response.ok) {\n let errorMsg = `Deletion failed with status ${response.status}`;\n try {\n const errJson = await response.json();\n errorMsg = errJson.message || errorMsg;\n } catch {\n const errText = await response.text();\n errorMsg = errText || errorMsg;\n }\n throw new Error(errorMsg);\n }\n\n return await response.json();\n }\n\n /**\n * Bulk delete multiple assets (up to 100 per request).\n */\n async bulkDeleteAssets(ids: string[]): Promise<{ success: boolean; deleted: number }> {\n const url = `${this.uploadUrl}/v1/assets/delete`;\n const response = await fetch(url, {\n method: 'POST',\n headers: {\n 'Authorization': `Bearer ${this.apiKey}`,\n 'Content-Type': 'application/json'\n },\n body: JSON.stringify({ ids })\n });\n\n if (!response.ok) {\n let errorMsg = `Bulk deletion failed with status ${response.status}`;\n try {\n const errJson = await response.json();\n errorMsg = errJson.message || errorMsg;\n } catch {\n const errText = await response.text();\n errorMsg = errText || errorMsg;\n }\n throw new Error(errorMsg);\n }\n\n return await response.json();\n }\n\n /**\n * Generate a CDN URL for an asset with transformation parameters or presets\n */\n getDeliveryUrl(path: string, options: { w?: number; h?: number; q?: number; f?: string, preset?: string } = {}): string {\n const url = new URL(`${this.fetchUrl}/${path}`);\n if (options.preset) url.searchParams.set('preset', options.preset);\n if (options.w) url.searchParams.set('w', options.w.toString());\n if (options.h) url.searchParams.set('h', options.h.toString());\n if (options.q) url.searchParams.set('q', options.q.toString());\n if (options.f) url.searchParams.set('f', options.f);\n return url.toString();\n }\n}\n\nexport * from './client-optimizer.js';\n"],"mappings":"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","names":["optimizeImageForUpload","file","maxDimension","quality","processableFile","heic2any","convertedBlob","err","resolve","reject","reader","e","_a","img","width","height","shouldResize","canvas","ctx","blob","newName","optimizedFile","PichaFlowClient","config","file","options","formData","xhr","url","resolve","reject","event","progress","error","signResponse","signature","timestamp","tenantId","id","response","errorMsg","ids","path"]}
package/package.json ADDED
@@ -0,0 +1,38 @@
1
+ {
2
+ "name": "@pichaflow/sdk",
3
+ "version": "0.1.0",
4
+ "description": "PichaFlow Core TypeScript SDK - Logic for edge-native media transformations.",
5
+ "main": "dist/index.js",
6
+ "module": "dist/index.mjs",
7
+ "types": "dist/index.d.ts",
8
+ "exports": {
9
+ ".": {
10
+ "types": "./dist/index.d.ts",
11
+ "nuxt": "./src/index.ts",
12
+ "development": "./src/index.ts",
13
+ "import": "./dist/index.mjs",
14
+ "require": "./dist/index.js"
15
+ }
16
+ },
17
+ "files": [
18
+ "dist"
19
+ ],
20
+ "license": "MIT",
21
+ "repository": {
22
+ "type": "git",
23
+ "url": "https://github.com/pichaflow/js-sdk.git"
24
+ },
25
+ "dependencies": {
26
+ "heic2any": "^0.0.4",
27
+ "zod": "^3.22.0"
28
+ },
29
+ "devDependencies": {
30
+ "vitest": "^4.1.8"
31
+ },
32
+ "scripts": {
33
+ "build": "tsup",
34
+ "dev": "tsup --watch",
35
+ "test": "vitest run",
36
+ "test:watch": "vitest"
37
+ }
38
+ }