@buildcores/render-client 1.0.7 → 1.0.9

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/api.d.ts CHANGED
@@ -1,7 +1,8 @@
1
- import { RenderBuildRequest, AvailablePartsResponse } from "./types";
1
+ import { RenderBuildRequest, AvailablePartsResponse, ApiConfig } from "./types";
2
2
  declare const API_BASE_URL = "https://www.renderapi.buildcores.com";
3
3
  export declare const API_ENDPOINTS: {
4
4
  readonly RENDER_BUILD_EXPERIMENTAL: "/render-build-experimental";
5
+ readonly RENDER_BUILD: "/render-build";
5
6
  readonly AVAILABLE_PARTS: "/available-parts";
6
7
  };
7
8
  export interface RenderBuildResponse {
@@ -18,6 +19,23 @@ export interface RenderBuildResponse {
18
19
  format?: string;
19
20
  };
20
21
  }
22
+ export interface RenderJobCreateResponse {
23
+ job_id: string;
24
+ status: "queued" | "processing" | "completed" | "error";
25
+ }
26
+ export interface RenderJobStatusResponse {
27
+ job_id: string;
28
+ status: "queued" | "processing" | "completed" | "error";
29
+ url?: string | null;
30
+ video_url?: string | null;
31
+ sprite_url?: string | null;
32
+ error?: string | null;
33
+ end_time?: string | null;
34
+ }
35
+ export interface RenderBuildAsyncResponse {
36
+ /** Final URL to the rendered MP4 (or sprite) asset */
37
+ videoUrl: string;
38
+ }
21
39
  export interface RenderSpriteResponse {
22
40
  /**
23
41
  * The rendered sprite sheet as a Blob (when format is "sprite")
@@ -49,13 +67,15 @@ export interface RenderAPIService {
49
67
  */
50
68
  getAvailableParts(config: ApiConfig): Promise<AvailablePartsResponse>;
51
69
  }
52
- export interface ApiConfig {
53
- environment?: "staging" | "prod";
54
- authToken?: string;
55
- }
56
70
  export declare const buildApiUrl: (endpoint: string, config: ApiConfig) => string;
57
71
  export declare const buildHeaders: (config: ApiConfig) => Record<string, string>;
58
72
  export declare const renderBuildExperimental: (request: RenderBuildRequest, config: ApiConfig) => Promise<RenderBuildResponse>;
73
+ export declare const createRenderBuildJob: (request: RenderBuildRequest, config: ApiConfig) => Promise<RenderJobCreateResponse>;
74
+ export declare const getRenderBuildStatus: (jobId: string, config: ApiConfig) => Promise<RenderJobStatusResponse>;
75
+ export declare const renderBuild: (request: RenderBuildRequest, config: ApiConfig, options?: {
76
+ pollIntervalMs?: number;
77
+ timeoutMs?: number;
78
+ }) => Promise<RenderBuildAsyncResponse>;
59
79
  export declare const renderSpriteExperimental: (request: RenderBuildRequest, config: ApiConfig) => Promise<RenderSpriteResponse>;
60
80
  export declare const getAvailableParts: (config: ApiConfig) => Promise<AvailablePartsResponse>;
61
81
  export { API_BASE_URL };
@@ -9,4 +9,12 @@ export interface UseBuildRenderReturn {
9
9
  isRenderingBuild: boolean;
10
10
  renderError: string | null;
11
11
  }
12
- export declare const useBuildRender: (parts: RenderBuildRequest, apiConfig: ApiConfig, onLoadStart?: () => void) => UseBuildRenderReturn;
12
+ export interface UseBuildRenderOptions {
13
+ /**
14
+ * Choose which backend flow to use
15
+ * - 'async' (default): uses /render-build and polls /render-build/{jobId}
16
+ * - 'experimental': uses /render-build-experimental and returns Blob
17
+ */
18
+ mode?: "async" | "experimental";
19
+ }
20
+ export declare const useBuildRender: (parts: RenderBuildRequest, apiConfig: ApiConfig, onLoadStart?: () => void, options?: UseBuildRenderOptions) => UseBuildRenderReturn;
@@ -9,4 +9,12 @@ export interface UseSpriteRenderReturn {
9
9
  totalFrames: number;
10
10
  } | null;
11
11
  }
12
- export declare const useSpriteRender: (parts: RenderBuildRequest, apiConfig: ApiConfig, onLoadStart?: () => void) => UseSpriteRenderReturn;
12
+ export interface UseSpriteRenderOptions {
13
+ /**
14
+ * Choose which backend flow to use
15
+ * - 'async' (default): uses /render-build and polls /render-build/{jobId} with format 'sprite'
16
+ * - 'experimental': uses /render-build-experimental and returns Blob
17
+ */
18
+ mode?: "async" | "experimental";
19
+ }
20
+ export declare const useSpriteRender: (parts: RenderBuildRequest, apiConfig: ApiConfig, onLoadStart?: () => void, options?: UseSpriteRenderOptions) => UseSpriteRenderReturn;
package/dist/index.d.ts CHANGED
@@ -79,7 +79,7 @@ interface BuildRenderVideoProps {
79
79
  * />
80
80
  * ```
81
81
  */
82
- apiConfig: ApiConfig$1;
82
+ apiConfig: ApiConfig;
83
83
  /**
84
84
  * Optional mouse sensitivity for dragging (default: 0.005).
85
85
  *
@@ -188,7 +188,7 @@ interface BuildRenderProps {
188
188
  * />
189
189
  * ```
190
190
  */
191
- apiConfig: ApiConfig$1;
191
+ apiConfig: ApiConfig;
192
192
  /**
193
193
  * Optional mouse sensitivity for dragging (default: 0.05).
194
194
  *
@@ -211,7 +211,7 @@ interface BuildRenderProps {
211
211
  /**
212
212
  * API configuration for environment and authentication
213
213
  */
214
- interface ApiConfig$1 {
214
+ interface ApiConfig {
215
215
  /**
216
216
  * Environment to use for API requests
217
217
  * - 'staging': Development/testing environment
@@ -455,7 +455,15 @@ interface UseBuildRenderReturn {
455
455
  isRenderingBuild: boolean;
456
456
  renderError: string | null;
457
457
  }
458
- declare const useBuildRender: (parts: RenderBuildRequest, apiConfig: ApiConfig$1, onLoadStart?: () => void) => UseBuildRenderReturn;
458
+ interface UseBuildRenderOptions {
459
+ /**
460
+ * Choose which backend flow to use
461
+ * - 'async' (default): uses /render-build and polls /render-build/{jobId}
462
+ * - 'experimental': uses /render-build-experimental and returns Blob
463
+ */
464
+ mode?: "async" | "experimental";
465
+ }
466
+ declare const useBuildRender: (parts: RenderBuildRequest, apiConfig: ApiConfig, onLoadStart?: () => void, options?: UseBuildRenderOptions) => UseBuildRenderReturn;
459
467
 
460
468
  interface UseSpriteRenderReturn {
461
469
  spriteSrc: string | null;
@@ -467,7 +475,15 @@ interface UseSpriteRenderReturn {
467
475
  totalFrames: number;
468
476
  } | null;
469
477
  }
470
- declare const useSpriteRender: (parts: RenderBuildRequest, apiConfig: ApiConfig$1, onLoadStart?: () => void) => UseSpriteRenderReturn;
478
+ interface UseSpriteRenderOptions {
479
+ /**
480
+ * Choose which backend flow to use
481
+ * - 'async' (default): uses /render-build and polls /render-build/{jobId} with format 'sprite'
482
+ * - 'experimental': uses /render-build-experimental and returns Blob
483
+ */
484
+ mode?: "async" | "experimental";
485
+ }
486
+ declare const useSpriteRender: (parts: RenderBuildRequest, apiConfig: ApiConfig, onLoadStart?: () => void, options?: UseSpriteRenderOptions) => UseSpriteRenderReturn;
471
487
 
472
488
  interface DragIconProps {
473
489
  width?: number;
@@ -494,6 +510,7 @@ declare const InstructionTooltip: React__default.FC<InstructionTooltipProps>;
494
510
  declare const API_BASE_URL = "https://www.renderapi.buildcores.com";
495
511
  declare const API_ENDPOINTS: {
496
512
  readonly RENDER_BUILD_EXPERIMENTAL: "/render-build-experimental";
513
+ readonly RENDER_BUILD: "/render-build";
497
514
  readonly AVAILABLE_PARTS: "/available-parts";
498
515
  };
499
516
  interface RenderBuildResponse {
@@ -541,10 +558,6 @@ interface RenderAPIService {
541
558
  */
542
559
  getAvailableParts(config: ApiConfig): Promise<AvailablePartsResponse>;
543
560
  }
544
- interface ApiConfig {
545
- environment?: "staging" | "prod";
546
- authToken?: string;
547
- }
548
561
  declare const buildApiUrl: (endpoint: string, config: ApiConfig) => string;
549
562
  declare const buildHeaders: (config: ApiConfig) => Record<string, string>;
550
563
  declare const renderBuildExperimental: (request: RenderBuildRequest, config: ApiConfig) => Promise<RenderBuildResponse>;
@@ -552,4 +565,4 @@ declare const renderSpriteExperimental: (request: RenderBuildRequest, config: Ap
552
565
  declare const getAvailableParts: (config: ApiConfig) => Promise<AvailablePartsResponse>;
553
566
 
554
567
  export { API_BASE_URL, API_ENDPOINTS, BuildRender, BuildRenderVideo, DragIcon, InstructionTooltip, LoadingErrorOverlay, PartCategory, arePartsEqual, buildApiUrl, buildHeaders, calculateCircularFrame, calculateCircularTime, getAvailableParts, renderBuildExperimental, renderSpriteExperimental, useBouncePatternProgress, useBuildRender, useSpriteRender, useSpriteScrubbing, useVideoScrubbing };
555
- export type { ApiConfig$1 as ApiConfig, AvailablePartsResponse, BuildRenderProps, BuildRenderVideoProps, PartDetails, RenderAPIService, RenderBuildRequest, RenderBuildResponse, RenderSpriteResponse, UseBuildRenderReturn, UseSpriteRenderReturn };
568
+ export type { ApiConfig, AvailablePartsResponse, BuildRenderProps, BuildRenderVideoProps, PartDetails, RenderAPIService, RenderBuildRequest, RenderBuildResponse, RenderSpriteResponse, UseBuildRenderOptions, UseBuildRenderReturn, UseSpriteRenderOptions, UseSpriteRenderReturn };
package/dist/index.esm.js CHANGED
@@ -324,6 +324,7 @@ const API_BASE_URL = "https://www.renderapi.buildcores.com";
324
324
  // API Endpoints
325
325
  const API_ENDPOINTS = {
326
326
  RENDER_BUILD_EXPERIMENTAL: "/render-build-experimental",
327
+ RENDER_BUILD: "/render-build",
327
328
  AVAILABLE_PARTS: "/available-parts",
328
329
  };
329
330
  // API URL helpers
@@ -346,6 +347,7 @@ const buildHeaders = (config) => {
346
347
  }
347
348
  return headers;
348
349
  };
350
+ const sleep = (ms) => new Promise((resolve) => setTimeout(resolve, ms));
349
351
  // API Implementation
350
352
  const renderBuildExperimental = async (request, config) => {
351
353
  const requestWithFormat = {
@@ -369,6 +371,68 @@ const renderBuildExperimental = async (request, config) => {
369
371
  },
370
372
  };
371
373
  };
374
+ // New async endpoints implementation
375
+ const createRenderBuildJob = async (request, config) => {
376
+ const body = {
377
+ parts: request.parts,
378
+ // If provided, forward format; default handled server-side but we keep explicit default
379
+ ...(request.format ? { format: request.format } : {}),
380
+ };
381
+ const response = await fetch(buildApiUrl(API_ENDPOINTS.RENDER_BUILD, config), {
382
+ method: "POST",
383
+ headers: buildHeaders(config),
384
+ body: JSON.stringify(body),
385
+ });
386
+ if (!response.ok) {
387
+ throw new Error(`Create render job failed: ${response.status} ${response.statusText}`);
388
+ }
389
+ const data = (await response.json());
390
+ if (!data?.job_id) {
391
+ throw new Error("Create render job failed: missing job_id in response");
392
+ }
393
+ return data;
394
+ };
395
+ const getRenderBuildStatus = async (jobId, config) => {
396
+ const url = buildApiUrl(`${API_ENDPOINTS.RENDER_BUILD}/${encodeURIComponent(jobId)}`, config);
397
+ const response = await fetch(url, {
398
+ method: "GET",
399
+ headers: buildHeaders(config),
400
+ });
401
+ if (response.status === 404) {
402
+ throw new Error("Render job not found");
403
+ }
404
+ if (!response.ok) {
405
+ throw new Error(`Get render job status failed: ${response.status} ${response.statusText}`);
406
+ }
407
+ return (await response.json());
408
+ };
409
+ const renderBuild = async (request, config, options) => {
410
+ const pollIntervalMs = 1500;
411
+ const timeoutMs = 120000; // 2 minutes default
412
+ const { job_id } = await createRenderBuildJob(request, config);
413
+ const start = Date.now();
414
+ // Poll until completed or error or timeout
415
+ for (;;) {
416
+ const status = await getRenderBuildStatus(job_id, config);
417
+ if (status.status === "completed") {
418
+ const requestedFormat = request.format ?? "video";
419
+ const finalUrl = (requestedFormat === "sprite"
420
+ ? status.sprite_url || status.url || undefined
421
+ : status.video_url || status.url || undefined);
422
+ if (!finalUrl) {
423
+ throw new Error("Render job completed but no URL returned");
424
+ }
425
+ return { videoUrl: finalUrl };
426
+ }
427
+ if (status.status === "error") {
428
+ throw new Error(status.error || "Render job failed");
429
+ }
430
+ if (Date.now() - start > timeoutMs) {
431
+ throw new Error("Timed out waiting for render job to complete");
432
+ }
433
+ await sleep(pollIntervalMs);
434
+ }
435
+ };
372
436
  const renderSpriteExperimental = async (request, config) => {
373
437
  const requestWithFormat = {
374
438
  ...request,
@@ -473,7 +537,7 @@ const arePartsEqual = (parts1, parts2) => {
473
537
  }
474
538
  return true;
475
539
  };
476
- const useBuildRender = (parts, apiConfig, onLoadStart) => {
540
+ const useBuildRender = (parts, apiConfig, onLoadStart, options) => {
477
541
  const [videoSrc, setVideoSrc] = useState(null);
478
542
  const [isRenderingBuild, setIsRenderingBuild] = useState(false);
479
543
  const [renderError, setRenderError] = useState(null);
@@ -483,15 +547,27 @@ const useBuildRender = (parts, apiConfig, onLoadStart) => {
483
547
  setIsRenderingBuild(true);
484
548
  setRenderError(null);
485
549
  onLoadStart?.();
486
- const response = await renderBuildExperimental(currentParts, apiConfig);
487
- const objectUrl = URL.createObjectURL(response.video);
488
- // Clean up previous video URL before setting new one
489
- setVideoSrc((prevSrc) => {
490
- if (prevSrc) {
491
- URL.revokeObjectURL(prevSrc);
492
- }
493
- return objectUrl;
494
- });
550
+ const mode = options?.mode ?? "async";
551
+ if (mode === "experimental") {
552
+ const response = await renderBuildExperimental(currentParts, apiConfig);
553
+ const objectUrl = URL.createObjectURL(response.video);
554
+ setVideoSrc((prevSrc) => {
555
+ if (prevSrc && prevSrc.startsWith("blob:")) {
556
+ URL.revokeObjectURL(prevSrc);
557
+ }
558
+ return objectUrl;
559
+ });
560
+ }
561
+ else {
562
+ const { videoUrl } = await renderBuild(currentParts, apiConfig);
563
+ // Clean up previous object URL (if any) before setting new one
564
+ setVideoSrc((prevSrc) => {
565
+ if (prevSrc && prevSrc.startsWith("blob:")) {
566
+ URL.revokeObjectURL(prevSrc);
567
+ }
568
+ return videoUrl;
569
+ });
570
+ }
495
571
  }
496
572
  catch (error) {
497
573
  setRenderError(error instanceof Error ? error.message : "Failed to render build");
@@ -499,7 +575,7 @@ const useBuildRender = (parts, apiConfig, onLoadStart) => {
499
575
  finally {
500
576
  setIsRenderingBuild(false);
501
577
  }
502
- }, [apiConfig, onLoadStart]);
578
+ }, [apiConfig, onLoadStart, options?.mode]);
503
579
  // Effect to call API when parts content changes (using custom equality check)
504
580
  useEffect(() => {
505
581
  const shouldFetch = previousPartsRef.current === null ||
@@ -512,7 +588,7 @@ const useBuildRender = (parts, apiConfig, onLoadStart) => {
512
588
  // Cleanup effect for component unmount
513
589
  useEffect(() => {
514
590
  return () => {
515
- if (videoSrc) {
591
+ if (videoSrc && videoSrc.startsWith("blob:")) {
516
592
  URL.revokeObjectURL(videoSrc);
517
593
  }
518
594
  };
@@ -524,7 +600,7 @@ const useBuildRender = (parts, apiConfig, onLoadStart) => {
524
600
  };
525
601
  };
526
602
 
527
- const useSpriteRender = (parts, apiConfig, onLoadStart) => {
603
+ const useSpriteRender = (parts, apiConfig, onLoadStart, options) => {
528
604
  const [spriteSrc, setSpriteSrc] = useState(null);
529
605
  const [isRenderingSprite, setIsRenderingSprite] = useState(false);
530
606
  const [renderError, setRenderError] = useState(null);
@@ -535,21 +611,36 @@ const useSpriteRender = (parts, apiConfig, onLoadStart) => {
535
611
  setIsRenderingSprite(true);
536
612
  setRenderError(null);
537
613
  onLoadStart?.();
538
- const response = await renderSpriteExperimental(currentParts, apiConfig);
539
- const objectUrl = URL.createObjectURL(response.sprite);
540
- // Clean up previous sprite URL before setting new one
541
- setSpriteSrc((prevSrc) => {
542
- if (prevSrc) {
543
- URL.revokeObjectURL(prevSrc);
544
- }
545
- return objectUrl;
546
- });
547
- // Set sprite metadata
548
- setSpriteMetadata({
549
- cols: response.metadata?.cols || 12,
550
- rows: response.metadata?.rows || 6,
551
- totalFrames: response.metadata?.totalFrames || 72,
552
- });
614
+ const mode = options?.mode ?? "async";
615
+ if (mode === "experimental") {
616
+ const response = await renderSpriteExperimental(currentParts, apiConfig);
617
+ const objectUrl = URL.createObjectURL(response.sprite);
618
+ // Clean up previous sprite URL before setting new one
619
+ setSpriteSrc((prevSrc) => {
620
+ if (prevSrc && prevSrc.startsWith("blob:")) {
621
+ URL.revokeObjectURL(prevSrc);
622
+ }
623
+ return objectUrl;
624
+ });
625
+ // Set sprite metadata
626
+ setSpriteMetadata({
627
+ cols: response.metadata?.cols || 12,
628
+ rows: response.metadata?.rows || 6,
629
+ totalFrames: response.metadata?.totalFrames || 72,
630
+ });
631
+ }
632
+ else {
633
+ // Async job-based flow: request sprite format and use returned URL
634
+ const { videoUrl: spriteUrl } = await renderBuild({ ...currentParts, format: "sprite" }, apiConfig);
635
+ setSpriteSrc((prevSrc) => {
636
+ if (prevSrc && prevSrc.startsWith("blob:")) {
637
+ URL.revokeObjectURL(prevSrc);
638
+ }
639
+ return spriteUrl;
640
+ });
641
+ // No metadata from async endpoint; keep defaults
642
+ setSpriteMetadata({ cols: 12, rows: 6, totalFrames: 72 });
643
+ }
553
644
  }
554
645
  catch (error) {
555
646
  setRenderError(error instanceof Error ? error.message : "Failed to render sprite");
@@ -557,7 +648,7 @@ const useSpriteRender = (parts, apiConfig, onLoadStart) => {
557
648
  finally {
558
649
  setIsRenderingSprite(false);
559
650
  }
560
- }, [apiConfig, onLoadStart]);
651
+ }, [apiConfig, onLoadStart, options?.mode]);
561
652
  // Effect to call API when parts content changes (using custom equality check)
562
653
  useEffect(() => {
563
654
  const shouldFetch = previousPartsRef.current === null ||
@@ -570,7 +661,7 @@ const useSpriteRender = (parts, apiConfig, onLoadStart) => {
570
661
  // Cleanup effect for component unmount
571
662
  useEffect(() => {
572
663
  return () => {
573
- if (spriteSrc) {
664
+ if (spriteSrc && spriteSrc.startsWith("blob:")) {
574
665
  URL.revokeObjectURL(spriteSrc);
575
666
  }
576
667
  };