@glbforge/meshy
Advanced tools
| /** | ||
| * fal.ai client for open-weight image-to-3D models (Hunyuan3D, TRELLIS, | ||
| * TripoSR). Same queue-poll-download shape as the Meshy client, so the | ||
| * worker and studio treat every generation provider identically. | ||
| * | ||
| * Schema tolerance: fal model outputs differ per model and drift over | ||
| * time, so results are deep-scanned for the first .glb URL instead of | ||
| * hardcoding response paths. | ||
| */ | ||
| export declare const FAL_MODELS: { | ||
| /** Tencent Hunyuan3D 2 — highest quality of the open trio. */ | ||
| readonly hunyuan: "fal-ai/hunyuan3d/v2"; | ||
| /** Microsoft TRELLIS — strong quality/speed balance. */ | ||
| readonly trellis: "fal-ai/trellis"; | ||
| /** TripoSR — fastest and cheapest, lighter on detail. */ | ||
| readonly triposr: "fal-ai/triposr"; | ||
| }; | ||
| export type FalModelKey = keyof typeof FAL_MODELS; | ||
| /** Queue routes use the root app id: 'fal-ai/hunyuan3d/v2' submits on the | ||
| * full path but polls on 'fal-ai/hunyuan3d'. */ | ||
| export declare function rootAppId(model: string): string; | ||
| export declare class FalError extends Error { | ||
| readonly status?: number | undefined; | ||
| constructor(message: string, status?: number | undefined); | ||
| } | ||
| export interface FalClientOptions { | ||
| /** Defaults to process.env.FAL_KEY. */ | ||
| apiKey?: string; | ||
| baseUrl?: string; | ||
| fetch?: typeof globalThis.fetch; | ||
| } | ||
| /** Depth-first scan for the first URL ending in .glb anywhere in a payload. */ | ||
| export declare function findGlbUrl(value: unknown): string | null; | ||
| export declare class FalClient { | ||
| private readonly apiKey; | ||
| private readonly baseUrl; | ||
| private readonly fetch; | ||
| constructor(opts?: FalClientOptions); | ||
| private request; | ||
| /** Submit an image (data URI or URL). Returns the queue request id. */ | ||
| submit(model: string, imageUrl: string, opts?: { | ||
| textured?: boolean; | ||
| }): Promise<string>; | ||
| status(model: string, requestId: string): Promise<{ | ||
| status: 'IN_QUEUE' | 'IN_PROGRESS' | 'COMPLETED' | string; | ||
| queuePosition: number | null; | ||
| }>; | ||
| /** Fetch the finished result and extract its GLB URL. */ | ||
| resultGlbUrl(model: string, requestId: string): Promise<string>; | ||
| downloadGlb(url: string): Promise<Uint8Array>; | ||
| } |
+127
| /** | ||
| * fal.ai client for open-weight image-to-3D models (Hunyuan3D, TRELLIS, | ||
| * TripoSR). Same queue-poll-download shape as the Meshy client, so the | ||
| * worker and studio treat every generation provider identically. | ||
| * | ||
| * Schema tolerance: fal model outputs differ per model and drift over | ||
| * time, so results are deep-scanned for the first .glb URL instead of | ||
| * hardcoding response paths. | ||
| */ | ||
| export const FAL_MODELS = { | ||
| /** Tencent Hunyuan3D 2 — highest quality of the open trio. */ | ||
| hunyuan: 'fal-ai/hunyuan3d/v2', | ||
| /** Microsoft TRELLIS — strong quality/speed balance. */ | ||
| trellis: 'fal-ai/trellis', | ||
| /** TripoSR — fastest and cheapest, lighter on detail. */ | ||
| triposr: 'fal-ai/triposr', | ||
| }; | ||
| /** Queue routes use the root app id: 'fal-ai/hunyuan3d/v2' submits on the | ||
| * full path but polls on 'fal-ai/hunyuan3d'. */ | ||
| export function rootAppId(model) { | ||
| return model.split('/').slice(0, 2).join('/'); | ||
| } | ||
| export class FalError extends Error { | ||
| status; | ||
| constructor(message, status) { | ||
| super(message); | ||
| this.status = status; | ||
| this.name = 'FalError'; | ||
| } | ||
| } | ||
| /** Depth-first scan for the first URL ending in .glb anywhere in a payload. */ | ||
| export function findGlbUrl(value) { | ||
| if (typeof value === 'string') { | ||
| return /^https?:\/\/\S+\.glb(\?\S*)?$/i.test(value) ? value : null; | ||
| } | ||
| if (Array.isArray(value)) { | ||
| for (const item of value) { | ||
| const hit = findGlbUrl(item); | ||
| if (hit) | ||
| return hit; | ||
| } | ||
| return null; | ||
| } | ||
| if (value && typeof value === 'object') { | ||
| for (const key of Object.keys(value)) { | ||
| const hit = findGlbUrl(value[key]); | ||
| if (hit) | ||
| return hit; | ||
| } | ||
| } | ||
| return null; | ||
| } | ||
| export class FalClient { | ||
| apiKey; | ||
| baseUrl; | ||
| fetch; | ||
| constructor(opts = {}) { | ||
| const apiKey = opts.apiKey ?? process.env.FAL_KEY; | ||
| if (!apiKey) { | ||
| throw new FalError('Missing fal.ai API key. Set FAL_KEY (keys: https://fal.ai/dashboard/keys) ' + | ||
| 'or pass { apiKey } explicitly.'); | ||
| } | ||
| this.apiKey = apiKey; | ||
| this.baseUrl = (opts.baseUrl ?? 'https://queue.fal.run').replace(/\/$/, ''); | ||
| this.fetch = opts.fetch ?? globalThis.fetch; | ||
| } | ||
| async request(method, path, body) { | ||
| const res = await this.fetch(this.baseUrl + path, { | ||
| method, | ||
| headers: { | ||
| authorization: `Key ${this.apiKey}`, | ||
| ...(body !== undefined && { 'content-type': 'application/json' }), | ||
| }, | ||
| body: body !== undefined ? JSON.stringify(body) : undefined, | ||
| }); | ||
| const text = await res.text(); | ||
| let json; | ||
| try { | ||
| json = text ? JSON.parse(text) : undefined; | ||
| } | ||
| catch { | ||
| json = text; | ||
| } | ||
| if (!res.ok) { | ||
| const detail = json?.detail; | ||
| throw new FalError(typeof detail === 'string' ? detail : `fal ${method} ${path} failed with HTTP ${res.status}`, res.status); | ||
| } | ||
| return json; | ||
| } | ||
| /** Submit an image (data URI or URL). Returns the queue request id. */ | ||
| async submit(model, imageUrl, opts = {}) { | ||
| // Models disagree on input keys; send the common aliases together — | ||
| // unknown keys are ignored. Hunyuan's paint stage only runs when | ||
| // textured_mesh is set; TRELLIS/TripoSR texture natively. | ||
| const textured = opts.textured !== false; | ||
| const res = await this.request('POST', `/${model}`, { | ||
| image_url: imageUrl, | ||
| input_image_url: imageUrl, | ||
| input_image_urls: [imageUrl], | ||
| textured_mesh: textured, | ||
| texture: textured, | ||
| }); | ||
| if (!res.request_id) | ||
| throw new FalError('fal did not return a request id'); | ||
| return res.request_id; | ||
| } | ||
| async status(model, requestId) { | ||
| const res = await this.request('GET', `/${rootAppId(model)}/requests/${requestId}/status`); | ||
| return { status: res.status, queuePosition: res.queue_position ?? null }; | ||
| } | ||
| /** Fetch the finished result and extract its GLB URL. */ | ||
| async resultGlbUrl(model, requestId) { | ||
| const res = await this.request('GET', `/${rootAppId(model)}/requests/${requestId}`); | ||
| const url = findGlbUrl(res); | ||
| if (!url) { | ||
| throw new FalError(`No .glb in the ${model} result — the model may output a different format. ` + | ||
| `Top-level keys: ${Object.keys(res ?? {}).join(', ')}`); | ||
| } | ||
| return url; | ||
| } | ||
| async downloadGlb(url) { | ||
| const res = await this.fetch(url); | ||
| if (!res.ok) | ||
| throw new FalError(`model download failed: HTTP ${res.status}`, res.status); | ||
| return new Uint8Array(await res.arrayBuffer()); | ||
| } | ||
| } |
+1
-0
| export * from './types.js'; | ||
| export { MeshyClient, MeshyError, type MeshyClientOptions } from './client.js'; | ||
| export { FalClient, FalError, FAL_MODELS, findGlbUrl, rootAppId, type FalModelKey, type FalClientOptions } from './fal.js'; |
+1
-0
| export * from './types.js'; | ||
| export { MeshyClient, MeshyError } from './client.js'; | ||
| export { FalClient, FalError, FAL_MODELS, findGlbUrl, rootAppId } from './fal.js'; |
+1
-1
| { | ||
| "name": "@glbforge/meshy", | ||
| "version": "0.3.0", | ||
| "version": "0.4.0", | ||
| "type": "module", | ||
@@ -5,0 +5,0 @@ "main": "dist/index.js", |
Environment variable access
Supply chain riskPackage accesses environment variables, which may be a sign of credential stuffing or data theft.
URL strings
Supply chain riskPackage contains fragments of external URLs or IP addresses, which the package may be accessing at runtime.
URL strings
Supply chain riskPackage contains fragments of external URLs or IP addresses, which the package may be accessing at runtime.
19696
57.54%11
22.22%439
69.5%3
50%18
100%