@designfever/web-review-kit 0.7.1 → 0.7.3

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/README.md CHANGED
@@ -28,7 +28,7 @@ This package does not own internal operator tools, private admin keys, or produc
28
28
  - [Architecture and runtime logic](docs/architecture.md): core runtime, React shell, coordinate, anchor, and extension boundaries.
29
29
  - [Figma overlay](docs/figma-overlay.md): how the shell toggles a host Figma overlay.
30
30
  - [Grid overlay](docs/grid-overlay.md): how the shell toggles a host grid/helper overlay.
31
- - [Release notes 0.7.0](docs/release-notes-0.7.0.md): latest Figma image, QA field, and remote adapter loading changes.
31
+ - [Release notes 0.7.3](docs/release-notes-0.7.3.md): latest endpoint Figma image store changes.
32
32
 
33
33
  ## Quick Start
34
34
 
@@ -217,8 +217,14 @@ function getReviewFigmaAssetMimeType(storageKey) {
217
217
  // src/figma/image.store.ts
218
218
  var DEFAULT_REVIEW_FIGMA_IMAGE_STORE_ENDPOINT = "/__dfwr/figma-images";
219
219
  function createReviewFigmaImageStoreClient(options = {}) {
220
+ return createEndpointReviewFigmaImageStore(options);
221
+ }
222
+ function createEndpointReviewFigmaImageStore(options = {}) {
220
223
  const endpoint = options.endpoint ?? DEFAULT_REVIEW_FIGMA_IMAGE_STORE_ENDPOINT;
221
- const request = createReviewFigmaImageStoreRequest(endpoint, options.fetch);
224
+ const request = createReviewFigmaImageStoreRequest(
225
+ options.fetch,
226
+ options.headers
227
+ );
222
228
  return {
223
229
  listImages(target) {
224
230
  const url = `${endpoint}?target=${encodeURIComponent(
@@ -265,10 +271,12 @@ async function createClientRenderedAddImageInput(input, clientRender, fetchOptio
265
271
  const token = readClientRenderToken(options);
266
272
  if (!token) return input;
267
273
  try {
268
- const asset = await withTimeout(
269
- createClientRenderedFigmaAsset(input.figmaUrl, token, options, fetchOption),
270
- options.timeoutMs ?? 1e4
271
- );
274
+ const asset = await createReviewFigmaClientRenderedAsset({
275
+ ...options,
276
+ fetch: fetchOption,
277
+ figmaUrl: input.figmaUrl,
278
+ token
279
+ });
272
280
  return {
273
281
  ...input,
274
282
  imageFormat: asset.imageFormat,
@@ -340,6 +348,17 @@ async function createClientRenderedFigmaAsset(figmaUrl, token, options, fetchOpt
340
348
  height: dimensions.height
341
349
  };
342
350
  }
351
+ function createReviewFigmaClientRenderedAsset({
352
+ fetch: fetchOption,
353
+ figmaUrl,
354
+ token,
355
+ ...options
356
+ }) {
357
+ return withTimeout(
358
+ createClientRenderedFigmaAsset(figmaUrl, token, options, fetchOption),
359
+ options.timeoutMs ?? 1e4
360
+ );
361
+ }
343
362
  async function readImageBlobDimensions(blob) {
344
363
  const image = await loadImageBlob(blob);
345
364
  return {
@@ -426,19 +445,27 @@ function getReviewFigmaImageMimeType(format) {
426
445
  if (format === "png") return "image/png";
427
446
  return "image/webp";
428
447
  }
429
- function createReviewFigmaImageStoreRequest(endpoint, fetchOption) {
448
+ function createReviewFigmaImageStoreRequest(fetchOption, headersProvider) {
430
449
  return async (input, init = {}) => {
431
450
  const requestFetch = fetchOption ?? globalThis.fetch;
432
451
  if (!requestFetch) throw new Error("Figma image store requires fetch.");
433
452
  const figmaToken = init.figmaToken ?? "";
434
- const { figmaToken: _figmaToken, ...requestInit } = init;
453
+ const {
454
+ figmaToken: _figmaToken,
455
+ headers: requestHeaders,
456
+ ...requestInit
457
+ } = init;
458
+ const headers = new Headers();
459
+ headers.set("Content-Type", "application/json");
460
+ if (figmaToken) headers.set("X-Figma-Token", figmaToken);
461
+ appendReviewFigmaImageStoreHeaders(
462
+ headers,
463
+ await readReviewFigmaImageStoreHeaders(headersProvider)
464
+ );
465
+ appendReviewFigmaImageStoreHeaders(headers, requestHeaders);
435
466
  const response = await requestFetch(input, {
436
467
  ...requestInit,
437
- headers: {
438
- "Content-Type": "application/json",
439
- ...figmaToken ? { "X-Figma-Token": figmaToken } : {},
440
- ...requestInit.headers ?? {}
441
- }
468
+ headers
442
469
  });
443
470
  const text = await response.text();
444
471
  const body = text ? JSON.parse(text) : null;
@@ -449,6 +476,15 @@ function createReviewFigmaImageStoreRequest(endpoint, fetchOption) {
449
476
  return body;
450
477
  };
451
478
  }
479
+ async function readReviewFigmaImageStoreHeaders(provider) {
480
+ return typeof provider === "function" ? provider() : provider;
481
+ }
482
+ function appendReviewFigmaImageStoreHeaders(target, source) {
483
+ if (!source) return;
484
+ new Headers(source).forEach((value, key) => {
485
+ target.set(key, value);
486
+ });
487
+ }
452
488
  function readReviewFigmaImageToken(provider) {
453
489
  const token = typeof provider === "function" ? provider() : provider;
454
490
  return typeof token === "string" ? token.trim() : "";
@@ -593,10 +629,12 @@ export {
593
629
  createReviewFigmaImageApiUrl,
594
630
  DEFAULT_REVIEW_FIGMA_IMAGE_STORE_ENDPOINT,
595
631
  createReviewFigmaImageStoreClient,
632
+ createEndpointReviewFigmaImageStore,
633
+ createReviewFigmaClientRenderedAsset,
596
634
  getReviewFigmaImageTargetKey,
597
635
  getReviewFigmaImageMimeType,
598
636
  createReviewFigmaImagesSnapshot,
599
637
  createReviewFigmaReleaseSnapshot,
600
638
  collectReviewFigmaReleaseSnapshot
601
639
  };
602
- //# sourceMappingURL=chunk-2ZLU5FTD.js.map
640
+ //# sourceMappingURL=chunk-UNDQZ4Y2.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/figma/parse.ts","../src/figma/token.ts","../src/figma/render.ts","../src/vite/figma-asset.ts","../src/figma/image.store.ts","../src/figma/image.snapshot.ts"],"sourcesContent":["export type ReviewFigmaNodeRef = {\n fileKey: string;\n nodeId: string;\n sourceUrl?: string;\n};\n\nexport const FIGMA_NODE_REF_SEPARATOR = '->';\n\nexport function parseReviewFigmaNodeRef(\n value: string | ReviewFigmaNodeRef\n): ReviewFigmaNodeRef | null {\n if (typeof value !== 'string') return normalizeReviewFigmaNodeRef(value);\n\n const input = value.trim();\n if (!input) return null;\n\n return parseReviewFigmaNodeRefValue(input) ?? parseReviewFigmaUrl(input);\n}\n\nexport function requireReviewFigmaNodeRef(\n value: string | ReviewFigmaNodeRef\n): ReviewFigmaNodeRef {\n const ref = parseReviewFigmaNodeRef(value);\n if (!ref) {\n throw new Error('A Figma node link or fileKey->nodeId value is required.');\n }\n return ref;\n}\n\nexport function createReviewFigmaNodeValue(ref: ReviewFigmaNodeRef) {\n return `${ref.fileKey}${FIGMA_NODE_REF_SEPARATOR}${ref.nodeId}`;\n}\n\nexport function createReviewFigmaFrameUrl(\n value: string | ReviewFigmaNodeRef\n) {\n const ref = parseReviewFigmaNodeRef(value);\n if (!ref) return null;\n\n return `https://www.figma.com/design/${encodeURIComponent(\n ref.fileKey\n )}?node-id=${encodeURIComponent(ref.nodeId.replace(/:/g, '-'))}`;\n}\n\nfunction parseReviewFigmaNodeRefValue(value: string) {\n const [fileKey, nodeId, extra] = value\n .split(FIGMA_NODE_REF_SEPARATOR)\n .map((part) => part.trim());\n\n if (!fileKey || !nodeId || extra !== undefined) return null;\n\n return normalizeReviewFigmaNodeRef({ fileKey, nodeId });\n}\n\nfunction parseReviewFigmaUrl(value: string) {\n let url: URL;\n try {\n url = new URL(value);\n } catch {\n return null;\n }\n\n const fileKey = getFigmaFileKey(url);\n const nodeId = normalizeFigmaNodeId(url.searchParams.get('node-id'));\n if (!fileKey || !nodeId) return null;\n\n return { fileKey, nodeId, sourceUrl: value };\n}\n\nfunction getFigmaFileKey(url: URL) {\n if (!/(^|\\.)figma\\.com$/i.test(url.hostname)) return null;\n\n const parts = url.pathname.split('/').filter(Boolean);\n const fileKeyIndex = parts.findIndex((part) =>\n ['design', 'file', 'proto', 'board'].includes(part)\n );\n const fileKey = fileKeyIndex >= 0 ? parts[fileKeyIndex + 1] : null;\n\n return normalizeFigmaFileKey(fileKey);\n}\n\nfunction normalizeReviewFigmaNodeRef(\n value: ReviewFigmaNodeRef | null | undefined\n) {\n const fileKey = normalizeFigmaFileKey(value?.fileKey);\n const nodeId = normalizeFigmaNodeId(value?.nodeId);\n if (!fileKey || !nodeId) return null;\n const sourceUrl = normalizeOptionalString(value?.sourceUrl);\n\n return sourceUrl ? { fileKey, nodeId, sourceUrl } : { fileKey, nodeId };\n}\n\nfunction normalizeFigmaFileKey(value: string | null | undefined) {\n return normalizeOptionalString(value)?.replace(/[?#].*$/, '') ?? null;\n}\n\nfunction normalizeFigmaNodeId(value: string | null | undefined) {\n const nodeId = normalizeOptionalString(value);\n if (!nodeId) return null;\n if (nodeId.includes(':')) return nodeId;\n return nodeId.replace(/-/g, ':');\n}\n\nfunction normalizeOptionalString(value: string | null | undefined) {\n if (typeof value !== 'string') return null;\n return value.trim() || null;\n}\n","export const DEFAULT_REVIEW_FIGMA_TOKEN_ENV_KEY = 'FIGMA_TOKEN';\nexport const REVIEW_FIGMA_TOKEN_MISSING_CODE = 'DFWR_FIGMA_TOKEN_MISSING';\n\nexport type ReviewFigmaTokenEnv = Record<\n string,\n string | null | undefined\n>;\n\nexport type ReviewFigmaTokenOptions = {\n token?: string | null;\n env?: ReviewFigmaTokenEnv;\n envKey?: string;\n enabled?: boolean;\n};\n\nexport class ReviewFigmaTokenError extends Error {\n readonly code = REVIEW_FIGMA_TOKEN_MISSING_CODE;\n readonly envKey: string;\n\n constructor(envKey = DEFAULT_REVIEW_FIGMA_TOKEN_ENV_KEY) {\n super(\n `Figma image rendering requires server env ${envKey}. Set ${envKey} in the dev/server environment; do not expose it as VITE_${envKey}.`\n );\n this.name = 'ReviewFigmaTokenError';\n this.envKey = envKey;\n }\n}\n\nexport function readReviewFigmaToken(\n options: ReviewFigmaTokenOptions = {}\n): string | null {\n if (options.enabled === false) return null;\n\n const envKey = options.envKey ?? DEFAULT_REVIEW_FIGMA_TOKEN_ENV_KEY;\n return normalizeReviewFigmaToken(options.token ?? options.env?.[envKey]);\n}\n\nexport function requireReviewFigmaToken(\n options: ReviewFigmaTokenOptions = {}\n): string {\n const envKey = options.envKey ?? DEFAULT_REVIEW_FIGMA_TOKEN_ENV_KEY;\n const token = readReviewFigmaToken(options);\n\n if (!token) throw new ReviewFigmaTokenError(envKey);\n return token;\n}\n\nexport function isReviewFigmaTokenError(\n error: unknown\n): error is ReviewFigmaTokenError {\n if (!error || typeof error !== 'object') return false;\n\n return (\n error instanceof ReviewFigmaTokenError ||\n ('code' in error && error.code === REVIEW_FIGMA_TOKEN_MISSING_CODE)\n );\n}\n\nfunction normalizeReviewFigmaToken(value: string | null | undefined) {\n if (typeof value !== 'string') return null;\n return value.trim() || null;\n}\n","import {\n requireReviewFigmaNodeRef,\n type ReviewFigmaNodeRef,\n} from './parse';\nimport { requireReviewFigmaToken } from './token';\n\nexport type ReviewFigmaRenderFormat = 'png' | 'jpg' | 'svg' | 'pdf';\n\nexport type ReviewFigmaImageRenderOptions = {\n figmaUrl: string | ReviewFigmaNodeRef;\n token?: string | null;\n format?: ReviewFigmaRenderFormat;\n scale?: number;\n useAbsoluteBounds?: boolean;\n apiBaseUrl?: string;\n fetch?: typeof fetch;\n signal?: AbortSignal;\n};\n\nexport type ReviewFigmaRenderedImage = {\n fileKey: string;\n nodeId: string;\n figmaUrl?: string;\n imageUrl: string;\n renderFormat: ReviewFigmaRenderFormat;\n};\n\ntype FigmaImageResponse = {\n err?: string | null;\n images?: Record<string, string | null | undefined>;\n};\n\nconst DEFAULT_FIGMA_API_BASE_URL = 'https://api.figma.com';\n\nexport async function renderReviewFigmaImage(\n options: ReviewFigmaImageRenderOptions\n): Promise<ReviewFigmaRenderedImage> {\n const token = requireReviewFigmaToken({ token: options.token });\n const ref = requireReviewFigmaNodeRef(options.figmaUrl);\n const renderFormat = options.format ?? 'png';\n const requestUrl = createReviewFigmaImageApiUrl({\n apiBaseUrl: options.apiBaseUrl,\n fileKey: ref.fileKey,\n nodeId: ref.nodeId,\n format: renderFormat,\n scale: options.scale,\n useAbsoluteBounds: options.useAbsoluteBounds,\n });\n const fetchImage = options.fetch ?? globalThis.fetch;\n if (!fetchImage) throw new Error('Figma image rendering requires fetch.');\n\n const response = await fetchImage(requestUrl, {\n headers: {\n 'X-Figma-Token': token,\n },\n signal: options.signal,\n });\n const body = (await response.json().catch(() => null)) as\n | FigmaImageResponse\n | null;\n\n if (!response.ok) {\n throw new Error(\n body?.err || `Figma image render failed with ${response.status}`\n );\n }\n\n const imageUrl = body?.images?.[ref.nodeId];\n if (!imageUrl) {\n throw new Error(`Figma image render returned no URL for ${ref.nodeId}.`);\n }\n\n return {\n fileKey: ref.fileKey,\n nodeId: ref.nodeId,\n figmaUrl: typeof options.figmaUrl === 'string'\n ? options.figmaUrl\n : options.figmaUrl.sourceUrl,\n imageUrl,\n renderFormat,\n };\n}\n\nexport function createReviewFigmaImageApiUrl({\n apiBaseUrl = DEFAULT_FIGMA_API_BASE_URL,\n fileKey,\n nodeId,\n format = 'png',\n scale,\n useAbsoluteBounds,\n}: {\n apiBaseUrl?: string;\n fileKey: string;\n nodeId: string;\n format?: ReviewFigmaRenderFormat;\n scale?: number;\n useAbsoluteBounds?: boolean;\n}) {\n const url = new URL(`/v1/images/${encodeURIComponent(fileKey)}`, apiBaseUrl);\n url.searchParams.set('ids', nodeId);\n url.searchParams.set('format', format);\n\n if (typeof scale === 'number' && Number.isFinite(scale) && scale > 0) {\n url.searchParams.set('scale', String(scale));\n }\n if (useAbsoluteBounds !== undefined) {\n url.searchParams.set('use_absolute_bounds', String(useAbsoluteBounds));\n }\n\n return url.toString();\n}\n","// Pure helpers for Figma image asset format conversion and storage-key handling.\n// Extracted from src/vite.ts to keep the plugin entry focused on request flow.\nimport type { ReviewFigmaImageFormat } from '../figma/image.types';\nimport type { ReviewFigmaRenderFormat } from '../figma/render';\n\nexport function parseReviewFigmaImageFormat(value: unknown) {\n return value === 'webp' || value === 'png' || value === 'jpg'\n ? value\n : undefined;\n}\n\nexport function getStoreRenderFormat(\n renderFormat: ReviewFigmaRenderFormat | undefined,\n imageFormat: ReviewFigmaImageFormat | undefined\n): Extract<ReviewFigmaRenderFormat, 'png' | 'jpg'> {\n if (renderFormat === 'jpg' || renderFormat === 'png') return renderFormat;\n if (imageFormat === 'jpg') return 'jpg';\n return 'png';\n}\n\nexport function getReviewFigmaImageFormatFromMimeType(\n mimeType: string\n): ReviewFigmaImageFormat | null {\n if (mimeType === 'image/webp') return 'webp';\n if (mimeType === 'image/png') return 'png';\n if (mimeType === 'image/jpeg') return 'jpg';\n return null;\n}\n\nexport function normalizeImageMimeType(value: string | null | undefined) {\n const mimeType = value?.split(';')[0]?.trim().toLowerCase();\n if (mimeType === 'image/jpg') return 'image/jpeg';\n if (\n mimeType === 'image/jpeg' ||\n mimeType === 'image/png' ||\n mimeType === 'image/webp'\n ) {\n return mimeType;\n }\n return null;\n}\n\nexport function createReviewFigmaAssetStorageKey(\n id: string,\n imageFormat: ReviewFigmaImageFormat\n) {\n return `${id}.${getReviewFigmaAssetExtension(imageFormat)}`;\n}\n\nexport function createReviewFigmaAssetUrl(\n assetEndpoint: string,\n storageKey: string\n) {\n return `${assetEndpoint}/${encodeURIComponent(storageKey)}`;\n}\n\nexport function getReviewFigmaAssetStorageKeyFromPathname(\n pathname: string,\n assetEndpoint: string\n) {\n try {\n const storageKey = decodeURIComponent(\n pathname.slice(assetEndpoint.length + 1)\n );\n return isSafeReviewFigmaAssetStorageKey(storageKey) ? storageKey : null;\n } catch {\n return null;\n }\n}\n\nexport function isSafeReviewFigmaAssetStorageKey(value: string) {\n return /^figma_[a-z0-9_]+\\.(webp|png|jpg)$/.test(value);\n}\n\nfunction getReviewFigmaAssetExtension(format: ReviewFigmaImageFormat) {\n return format === 'jpg' ? 'jpg' : format;\n}\n\nexport function getReviewFigmaAssetMimeType(storageKey: string) {\n if (storageKey.endsWith('.jpg')) return 'image/jpeg';\n if (storageKey.endsWith('.webp')) return 'image/webp';\n return 'image/png';\n}\n","import type {\n AddReviewFigmaImageInput,\n ReorderReviewFigmaImagesInput,\n ReviewFigmaImage,\n ReviewFigmaImageAssetInput,\n ReviewFigmaImageFormat,\n ReviewFigmaImageStore,\n ReviewFigmaImageTarget,\n} from './image.types';\nimport { getReviewFigmaImageFormatFromMimeType } from '../vite/figma-asset';\nimport { parseReviewFigmaNodeRef } from './parse';\nimport {\n createReviewFigmaImageApiUrl,\n type ReviewFigmaRenderFormat,\n} from './render';\n\nexport const DEFAULT_REVIEW_FIGMA_IMAGE_STORE_ENDPOINT =\n '/__dfwr/figma-images';\n\ntype ReviewFigmaImageTokenProvider =\n | string\n | null\n | undefined\n | (() => string | null | undefined);\n\nexport type ReviewFigmaImageStoreHeadersProvider =\n | HeadersInit\n | null\n | undefined\n | (() => HeadersInit | null | undefined | Promise<HeadersInit | null | undefined>);\n\nexport type ReviewFigmaImageStoreClientOptions = {\n endpoint?: string;\n fetch?: typeof fetch;\n token?: ReviewFigmaImageTokenProvider;\n clientRender?: boolean | ReviewFigmaImageClientRenderOptions;\n};\n\nexport type EndpointReviewFigmaImageStoreOptions =\n ReviewFigmaImageStoreClientOptions & {\n headers?: ReviewFigmaImageStoreHeadersProvider;\n };\n\nexport type ReviewFigmaImageClientRenderOptions = {\n token?: string | null | (() => string | null | undefined);\n apiBaseUrl?: string;\n renderFormat?: Extract<ReviewFigmaRenderFormat, 'png' | 'jpg'>;\n renderScale?: number;\n useAbsoluteBounds?: boolean;\n convertToWebp?: boolean;\n webpQuality?: number;\n timeoutMs?: number;\n};\n\nexport type CreateReviewFigmaClientRenderedAssetOptions =\n ReviewFigmaImageClientRenderOptions & {\n figmaUrl: string;\n token: string;\n fetch?: typeof fetch;\n };\n\nexport function createReviewFigmaImageStoreClient(\n options: ReviewFigmaImageStoreClientOptions = {}\n): ReviewFigmaImageStore {\n return createEndpointReviewFigmaImageStore(options);\n}\n\nexport function createEndpointReviewFigmaImageStore(\n options: EndpointReviewFigmaImageStoreOptions = {}\n): ReviewFigmaImageStore {\n const endpoint =\n options.endpoint ?? DEFAULT_REVIEW_FIGMA_IMAGE_STORE_ENDPOINT;\n const request = createReviewFigmaImageStoreRequest(\n options.fetch,\n options.headers\n );\n\n return {\n listImages(target) {\n const url = `${endpoint}?target=${encodeURIComponent(\n JSON.stringify(target)\n )}`;\n return request<ReviewFigmaImage[]>(url);\n },\n async addImage(input) {\n const nextInput = await createClientRenderedAddImageInput(\n input,\n options.clientRender,\n options.fetch\n );\n return request<ReviewFigmaImage>(endpoint, {\n method: 'POST',\n body: JSON.stringify(nextInput),\n figmaToken: readReviewFigmaImageToken(\n options.token ?? getStoredReviewFigmaImageToken\n ),\n });\n },\n updateImage(id, patch) {\n return request<ReviewFigmaImage>(`${endpoint}/${encodeURIComponent(id)}`, {\n method: 'PATCH',\n body: JSON.stringify(patch),\n });\n },\n reorderImages(input) {\n return request<ReviewFigmaImage[]>(`${endpoint}/reorder`, {\n method: 'PATCH',\n body: JSON.stringify(input),\n });\n },\n deleteImage(id) {\n return request<void>(`${endpoint}/${encodeURIComponent(id)}`, {\n method: 'DELETE',\n });\n },\n };\n}\n\nasync function createClientRenderedAddImageInput(\n input: AddReviewFigmaImageInput,\n clientRender: ReviewFigmaImageStoreClientOptions['clientRender'],\n fetchOption: typeof fetch | undefined\n): Promise<AddReviewFigmaImageInput> {\n const options = normalizeClientRenderOptions(clientRender);\n if (!options) return input;\n\n const token = readClientRenderToken(options);\n if (!token) return input;\n\n try {\n const asset = await createReviewFigmaClientRenderedAsset({\n ...options,\n fetch: fetchOption,\n figmaUrl: input.figmaUrl,\n token,\n });\n return {\n ...input,\n imageFormat: asset.imageFormat,\n asset,\n };\n } catch {\n return input;\n }\n}\n\nfunction normalizeClientRenderOptions(\n clientRender: ReviewFigmaImageStoreClientOptions['clientRender']\n): ReviewFigmaImageClientRenderOptions | null {\n if (!clientRender) return null;\n if (clientRender === true) return {};\n return clientRender;\n}\n\nfunction readClientRenderToken(options: ReviewFigmaImageClientRenderOptions) {\n const token =\n typeof options.token === 'function' ? options.token() : options.token;\n return typeof token === 'string' ? token.trim() : '';\n}\n\nasync function createClientRenderedFigmaAsset(\n figmaUrl: string,\n token: string,\n options: ReviewFigmaImageClientRenderOptions,\n fetchOption: typeof fetch | undefined\n): Promise<ReviewFigmaImageAssetInput> {\n const ref = parseReviewFigmaNodeRef(figmaUrl);\n if (!ref) throw new Error('A Figma node link is required.');\n\n const requestFetch = fetchOption ?? globalThis.fetch;\n if (!requestFetch) throw new Error('Figma client rendering requires fetch.');\n\n const renderFormat = options.renderFormat ?? 'png';\n const response = await requestFetch(\n createReviewFigmaImageApiUrl({\n apiBaseUrl: options.apiBaseUrl,\n fileKey: ref.fileKey,\n nodeId: ref.nodeId,\n format: renderFormat,\n scale: options.renderScale,\n useAbsoluteBounds: options.useAbsoluteBounds,\n }),\n {\n headers: {\n 'X-Figma-Token': token,\n },\n }\n );\n const body = (await response.json().catch(() => null)) as\n | { err?: string | null; images?: Record<string, string | null | undefined> }\n | null;\n if (!response.ok) {\n throw new Error(body?.err || `Figma image render failed with ${response.status}`);\n }\n\n const imageUrl = body?.images?.[ref.nodeId];\n if (!imageUrl) throw new Error(`Figma image render returned no URL for ${ref.nodeId}.`);\n\n const imageResponse = await requestFetch(imageUrl);\n if (!imageResponse.ok) {\n throw new Error(`Figma image download failed with ${imageResponse.status}`);\n }\n\n const originalBlob = await imageResponse.blob();\n const originalMimeType =\n normalizeClientImageMimeType(originalBlob.type) ??\n getReviewFigmaImageMimeType(renderFormat === 'jpg' ? 'jpg' : 'png');\n const originalFormat =\n getReviewFigmaImageFormatFromMimeType(originalMimeType) ??\n (renderFormat === 'jpg' ? 'jpg' : 'png');\n const dimensions = await readImageBlobDimensions(originalBlob);\n const shouldConvertToWebp = options.convertToWebp ?? true;\n const convertedBlob = shouldConvertToWebp\n ? await convertImageBlobToWebp(\n originalBlob,\n options.webpQuality ?? 0.9,\n dimensions\n ).catch(() => null)\n : null;\n const finalBlob =\n convertedBlob?.type === 'image/webp' ? convertedBlob : originalBlob;\n const finalMimeType =\n normalizeClientImageMimeType(finalBlob.type) ?? originalMimeType;\n const finalFormat =\n getReviewFigmaImageFormatFromMimeType(finalMimeType) ?? originalFormat;\n\n return {\n dataUrl: await blobToDataUrl(finalBlob),\n imageFormat: finalFormat,\n mimeType: finalMimeType,\n byteSize: finalBlob.size,\n width: dimensions.width,\n height: dimensions.height,\n };\n}\n\nexport function createReviewFigmaClientRenderedAsset({\n fetch: fetchOption,\n figmaUrl,\n token,\n ...options\n}: CreateReviewFigmaClientRenderedAssetOptions): Promise<ReviewFigmaImageAssetInput> {\n return withTimeout(\n createClientRenderedFigmaAsset(figmaUrl, token, options, fetchOption),\n options.timeoutMs ?? 10000\n );\n}\n\nasync function readImageBlobDimensions(blob: Blob) {\n const image = await loadImageBlob(blob);\n return {\n width: image.naturalWidth || image.width,\n height: image.naturalHeight || image.height,\n };\n}\n\nasync function convertImageBlobToWebp(\n blob: Blob,\n quality: number,\n dimensions: { width: number; height: number }\n) {\n if (typeof document === 'undefined') return null;\n if (!dimensions.width || !dimensions.height) return null;\n\n const image = await loadImageBlob(blob);\n const canvas = document.createElement('canvas');\n canvas.width = dimensions.width;\n canvas.height = dimensions.height;\n const context = canvas.getContext('2d');\n if (!context) return null;\n\n context.drawImage(image, 0, 0);\n\n return new Promise<Blob | null>((resolve) => {\n canvas.toBlob(resolve, 'image/webp', quality);\n });\n}\n\nfunction loadImageBlob(blob: Blob) {\n return new Promise<HTMLImageElement>((resolve, reject) => {\n if (typeof Image === 'undefined' || typeof URL === 'undefined') {\n reject(new Error('Image decoding is unavailable.'));\n return;\n }\n\n const image = new Image();\n const objectUrl = URL.createObjectURL(blob);\n image.onload = () => {\n URL.revokeObjectURL(objectUrl);\n resolve(image);\n };\n image.onerror = () => {\n URL.revokeObjectURL(objectUrl);\n reject(new Error('Image decoding failed.'));\n };\n image.src = objectUrl;\n });\n}\n\nfunction blobToDataUrl(blob: Blob) {\n return new Promise<string>((resolve, reject) => {\n const reader = new FileReader();\n reader.onload = () => {\n if (typeof reader.result === 'string') {\n resolve(reader.result);\n return;\n }\n reject(new Error('Blob encoding failed.'));\n };\n reader.onerror = () => reject(reader.error ?? new Error('Blob encoding failed.'));\n reader.readAsDataURL(blob);\n });\n}\n\nfunction normalizeClientImageMimeType(value: string | null | undefined) {\n const mimeType = value?.split(';')[0]?.trim().toLowerCase();\n if (mimeType === 'image/jpg') return 'image/jpeg';\n if (\n mimeType === 'image/jpeg' ||\n mimeType === 'image/png' ||\n mimeType === 'image/webp'\n ) {\n return mimeType;\n }\n return null;\n}\n\nasync function withTimeout<T>(promise: Promise<T>, timeoutMs: number) {\n let timeoutId: ReturnType<typeof setTimeout> | undefined;\n try {\n return await Promise.race([\n promise,\n new Promise<T>((_, reject) => {\n timeoutId = setTimeout(\n () => reject(new Error('Figma client rendering timed out.')),\n timeoutMs\n );\n }),\n ]);\n } finally {\n if (timeoutId) clearTimeout(timeoutId);\n }\n}\n\nexport function getReviewFigmaImageTargetKey(target: ReviewFigmaImageTarget) {\n return JSON.stringify(normalizeReviewFigmaImageTarget(target));\n}\n\nexport function getReviewFigmaImageMimeType(\n format: ReviewFigmaImageFormat\n) {\n if (format === 'jpg') return 'image/jpeg';\n if (format === 'png') return 'image/png';\n return 'image/webp';\n}\n\ntype ReviewFigmaImageStoreRequestInit = RequestInit & {\n figmaToken?: string;\n};\n\nfunction createReviewFigmaImageStoreRequest(\n fetchOption: typeof fetch | undefined,\n headersProvider: ReviewFigmaImageStoreHeadersProvider\n) {\n return async <T>(\n input: string,\n init: ReviewFigmaImageStoreRequestInit = {}\n ) => {\n const requestFetch = fetchOption ?? globalThis.fetch;\n if (!requestFetch) throw new Error('Figma image store requires fetch.');\n const figmaToken = init.figmaToken ?? '';\n const {\n figmaToken: _figmaToken,\n headers: requestHeaders,\n ...requestInit\n } = init;\n const headers = new Headers();\n headers.set('Content-Type', 'application/json');\n if (figmaToken) headers.set('X-Figma-Token', figmaToken);\n appendReviewFigmaImageStoreHeaders(\n headers,\n await readReviewFigmaImageStoreHeaders(headersProvider)\n );\n appendReviewFigmaImageStoreHeaders(headers, requestHeaders);\n\n const response = await requestFetch(input, {\n ...requestInit,\n headers,\n });\n const text = await response.text();\n const body = text ? JSON.parse(text) : null;\n\n if (!response.ok) {\n const message =\n typeof body?.error === 'string'\n ? body.error\n : `Figma image store request failed: ${response.status}`;\n throw new Error(message);\n }\n\n return body as T;\n };\n}\n\nasync function readReviewFigmaImageStoreHeaders(\n provider: ReviewFigmaImageStoreHeadersProvider\n) {\n return typeof provider === 'function' ? provider() : provider;\n}\n\nfunction appendReviewFigmaImageStoreHeaders(\n target: Headers,\n source: HeadersInit | null | undefined\n) {\n if (!source) return;\n new Headers(source).forEach((value, key) => {\n target.set(key, value);\n });\n}\n\nfunction readReviewFigmaImageToken(provider: ReviewFigmaImageTokenProvider) {\n const token = typeof provider === 'function' ? provider() : provider;\n return typeof token === 'string' ? token.trim() : '';\n}\n\nfunction getStoredReviewFigmaImageToken() {\n if (typeof window === 'undefined') return '';\n\n try {\n return window.localStorage.getItem('figma-token') ?? '';\n } catch {\n return '';\n }\n}\n\nfunction normalizeReviewFigmaImageTarget(target: ReviewFigmaImageTarget) {\n if (target.type === 'figma-node') {\n return {\n type: target.type,\n projectId: target.projectId,\n fileKey: target.fileKey,\n nodeId: target.nodeId,\n };\n }\n\n return {\n type: target.type,\n projectId: target.projectId,\n pageUrl: target.pageUrl,\n slot: target.slot ?? '',\n viewport: target.viewport\n ? {\n label: target.viewport.label ?? '',\n width: target.viewport.width ?? null,\n height: target.viewport.height ?? null,\n scope: target.viewport.scope ?? '',\n }\n : null,\n };\n}\n\nexport type { ReorderReviewFigmaImagesInput };\n","import type {\n ReviewFigmaImage,\n ReviewFigmaImageStore,\n ReviewFigmaImageTarget,\n} from './image.types';\nimport { getReviewFigmaImageTargetKey } from './image.store';\n\nexport type ReviewFigmaImagesSnapshot = ReviewFigmaImage[];\n\nexport type ReviewFigmaReleaseSnapshot = {\n version: 1;\n projectId: string;\n releaseId?: string;\n label?: string;\n createdAt: string;\n figmaImagesSnapshot: ReviewFigmaImagesSnapshot;\n};\n\nexport type CreateReviewFigmaImagesSnapshotOptions = {\n projectId?: string;\n targets?: readonly ReviewFigmaImageTarget[];\n};\n\nexport type CreateReviewFigmaReleaseSnapshotOptions =\n CreateReviewFigmaImagesSnapshotOptions & {\n images: readonly ReviewFigmaImage[];\n projectId: string;\n releaseId?: string;\n label?: string;\n createdAt?: string;\n };\n\nexport type CollectReviewFigmaReleaseSnapshotOptions = Omit<\n CreateReviewFigmaReleaseSnapshotOptions,\n 'images'\n> & {\n store: ReviewFigmaImageStore;\n targets: readonly ReviewFigmaImageTarget[];\n};\n\nexport function createReviewFigmaImagesSnapshot(\n images: readonly ReviewFigmaImage[],\n options: CreateReviewFigmaImagesSnapshotOptions = {}\n): ReviewFigmaImagesSnapshot {\n const targetKeys = options.targets?.length\n ? new Set(options.targets.map(getReviewFigmaImageTargetKey))\n : null;\n\n return images\n .filter((image) => {\n if (options.projectId && image.projectId !== options.projectId) {\n return false;\n }\n if (\n targetKeys &&\n !targetKeys.has(getReviewFigmaImageTargetKey(image.target))\n ) {\n return false;\n }\n return true;\n })\n .map(cloneReviewFigmaImage)\n .sort(compareReviewFigmaSnapshotImages);\n}\n\nexport function createReviewFigmaReleaseSnapshot({\n images,\n projectId,\n releaseId,\n label,\n createdAt,\n targets,\n}: CreateReviewFigmaReleaseSnapshotOptions): ReviewFigmaReleaseSnapshot {\n return {\n version: 1,\n projectId,\n ...(releaseId ? { releaseId } : null),\n ...(label ? { label } : null),\n createdAt: createdAt ?? new Date().toISOString(),\n figmaImagesSnapshot: createReviewFigmaImagesSnapshot(images, {\n projectId,\n targets,\n }),\n };\n}\n\nexport async function collectReviewFigmaReleaseSnapshot({\n store,\n targets,\n ...snapshotOptions\n}: CollectReviewFigmaReleaseSnapshotOptions): Promise<ReviewFigmaReleaseSnapshot> {\n const imagesByTarget = await Promise.all(\n targets.map((target) => store.listImages(target))\n );\n\n return createReviewFigmaReleaseSnapshot({\n ...snapshotOptions,\n targets,\n images: dedupeReviewFigmaImages(imagesByTarget.flat()),\n });\n}\n\nfunction dedupeReviewFigmaImages(images: readonly ReviewFigmaImage[]) {\n return Array.from(new Map(images.map((image) => [image.id, image])).values());\n}\n\nfunction cloneReviewFigmaImage(image: ReviewFigmaImage): ReviewFigmaImage {\n return {\n ...image,\n target: cloneReviewFigmaImageTarget(image.target),\n };\n}\n\nfunction cloneReviewFigmaImageTarget(\n target: ReviewFigmaImageTarget\n): ReviewFigmaImageTarget {\n if (target.type === 'figma-node') {\n return {\n type: target.type,\n projectId: target.projectId,\n fileKey: target.fileKey,\n nodeId: target.nodeId,\n };\n }\n\n return {\n type: target.type,\n projectId: target.projectId,\n pageUrl: target.pageUrl,\n slot: target.slot,\n viewport: target.viewport\n ? {\n label: target.viewport.label,\n width: target.viewport.width,\n height: target.viewport.height,\n scope: target.viewport.scope,\n }\n : undefined,\n };\n}\n\nfunction compareReviewFigmaSnapshotImages(\n a: ReviewFigmaImage,\n b: ReviewFigmaImage\n) {\n return (\n a.projectId.localeCompare(b.projectId) ||\n getReviewFigmaImageTargetKey(a.target).localeCompare(\n getReviewFigmaImageTargetKey(b.target)\n ) ||\n a.order - b.order ||\n a.createdAt.localeCompare(b.createdAt) ||\n a.id.localeCompare(b.id)\n );\n}\n"],"mappings":"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","names":[]}
package/dist/index.cjs CHANGED
@@ -22,6 +22,7 @@ var index_exports = {};
22
22
  __export(index_exports, {
23
23
  DEFAULT_REVIEW_FIGMA_IMAGE_FORMAT: () => DEFAULT_REVIEW_FIGMA_IMAGE_FORMAT,
24
24
  DEFAULT_REVIEW_FIGMA_IMAGE_STORE_ENDPOINT: () => DEFAULT_REVIEW_FIGMA_IMAGE_STORE_ENDPOINT,
25
+ DEFAULT_REVIEW_FIGMA_REMOTE_IMAGES_TABLE: () => DEFAULT_REVIEW_FIGMA_REMOTE_IMAGES_TABLE,
25
26
  DEFAULT_REVIEW_FIGMA_TOKEN_ENV_KEY: () => DEFAULT_REVIEW_FIGMA_TOKEN_ENV_KEY,
26
27
  DEFAULT_REVIEW_VIEWPORTS: () => DEFAULT_REVIEW_VIEWPORTS,
27
28
  FIGMA_NODE_REF_SEPARATOR: () => FIGMA_NODE_REF_SEPARATOR,
@@ -29,6 +30,8 @@ __export(index_exports, {
29
30
  REVIEW_WORKFLOW_STATUS_OPTIONS: () => REVIEW_WORKFLOW_STATUS_OPTIONS,
30
31
  ReviewFigmaTokenError: () => ReviewFigmaTokenError,
31
32
  collectReviewFigmaReleaseSnapshot: () => collectReviewFigmaReleaseSnapshot,
33
+ createEndpointReviewFigmaImageStore: () => createEndpointReviewFigmaImageStore,
34
+ createRemoteReviewFigmaImageStore: () => createRemoteReviewFigmaImageStore,
32
35
  createReviewFigmaFrameUrl: () => createReviewFigmaFrameUrl,
33
36
  createReviewFigmaImageStoreClient: () => createReviewFigmaImageStoreClient,
34
37
  createReviewFigmaImagesSnapshot: () => createReviewFigmaImagesSnapshot,
@@ -532,8 +535,14 @@ function createReviewFigmaImageApiUrl({
532
535
  // src/figma/image.store.ts
533
536
  var DEFAULT_REVIEW_FIGMA_IMAGE_STORE_ENDPOINT = "/__dfwr/figma-images";
534
537
  function createReviewFigmaImageStoreClient(options = {}) {
538
+ return createEndpointReviewFigmaImageStore(options);
539
+ }
540
+ function createEndpointReviewFigmaImageStore(options = {}) {
535
541
  const endpoint = options.endpoint ?? DEFAULT_REVIEW_FIGMA_IMAGE_STORE_ENDPOINT;
536
- const request = createReviewFigmaImageStoreRequest(endpoint, options.fetch);
542
+ const request = createReviewFigmaImageStoreRequest(
543
+ options.fetch,
544
+ options.headers
545
+ );
537
546
  return {
538
547
  listImages(target) {
539
548
  const url = `${endpoint}?target=${encodeURIComponent(
@@ -580,10 +589,12 @@ async function createClientRenderedAddImageInput(input, clientRender, fetchOptio
580
589
  const token = readClientRenderToken(options);
581
590
  if (!token) return input;
582
591
  try {
583
- const asset = await withTimeout(
584
- createClientRenderedFigmaAsset(input.figmaUrl, token, options, fetchOption),
585
- options.timeoutMs ?? 1e4
586
- );
592
+ const asset = await createReviewFigmaClientRenderedAsset({
593
+ ...options,
594
+ fetch: fetchOption,
595
+ figmaUrl: input.figmaUrl,
596
+ token
597
+ });
587
598
  return {
588
599
  ...input,
589
600
  imageFormat: asset.imageFormat,
@@ -655,6 +666,17 @@ async function createClientRenderedFigmaAsset(figmaUrl, token, options, fetchOpt
655
666
  height: dimensions.height
656
667
  };
657
668
  }
669
+ function createReviewFigmaClientRenderedAsset({
670
+ fetch: fetchOption,
671
+ figmaUrl,
672
+ token,
673
+ ...options
674
+ }) {
675
+ return withTimeout(
676
+ createClientRenderedFigmaAsset(figmaUrl, token, options, fetchOption),
677
+ options.timeoutMs ?? 1e4
678
+ );
679
+ }
658
680
  async function readImageBlobDimensions(blob) {
659
681
  const image = await loadImageBlob(blob);
660
682
  return {
@@ -741,19 +763,27 @@ function getReviewFigmaImageMimeType(format) {
741
763
  if (format === "png") return "image/png";
742
764
  return "image/webp";
743
765
  }
744
- function createReviewFigmaImageStoreRequest(endpoint, fetchOption) {
766
+ function createReviewFigmaImageStoreRequest(fetchOption, headersProvider) {
745
767
  return async (input, init = {}) => {
746
768
  const requestFetch = fetchOption ?? globalThis.fetch;
747
769
  if (!requestFetch) throw new Error("Figma image store requires fetch.");
748
770
  const figmaToken = init.figmaToken ?? "";
749
- const { figmaToken: _figmaToken, ...requestInit } = init;
771
+ const {
772
+ figmaToken: _figmaToken,
773
+ headers: requestHeaders,
774
+ ...requestInit
775
+ } = init;
776
+ const headers = new Headers();
777
+ headers.set("Content-Type", "application/json");
778
+ if (figmaToken) headers.set("X-Figma-Token", figmaToken);
779
+ appendReviewFigmaImageStoreHeaders(
780
+ headers,
781
+ await readReviewFigmaImageStoreHeaders(headersProvider)
782
+ );
783
+ appendReviewFigmaImageStoreHeaders(headers, requestHeaders);
750
784
  const response = await requestFetch(input, {
751
785
  ...requestInit,
752
- headers: {
753
- "Content-Type": "application/json",
754
- ...figmaToken ? { "X-Figma-Token": figmaToken } : {},
755
- ...requestInit.headers ?? {}
756
- }
786
+ headers
757
787
  });
758
788
  const text = await response.text();
759
789
  const body = text ? JSON.parse(text) : null;
@@ -764,6 +794,15 @@ function createReviewFigmaImageStoreRequest(endpoint, fetchOption) {
764
794
  return body;
765
795
  };
766
796
  }
797
+ async function readReviewFigmaImageStoreHeaders(provider) {
798
+ return typeof provider === "function" ? provider() : provider;
799
+ }
800
+ function appendReviewFigmaImageStoreHeaders(target, source) {
801
+ if (!source) return;
802
+ new Headers(source).forEach((value, key) => {
803
+ target.set(key, value);
804
+ });
805
+ }
767
806
  function readReviewFigmaImageToken(provider) {
768
807
  const token = typeof provider === "function" ? provider() : provider;
769
808
  return typeof token === "string" ? token.trim() : "";
@@ -799,6 +838,300 @@ function normalizeReviewFigmaImageTarget(target) {
799
838
  };
800
839
  }
801
840
 
841
+ // src/figma/remote.image.store.ts
842
+ var DEFAULT_REVIEW_FIGMA_REMOTE_IMAGES_TABLE = "review_figma_images";
843
+ var DEFAULT_REVIEW_FIGMA_REMOTE_UPLOAD_TIMEOUT_MS = 3e4;
844
+ var FIGMA_TOKEN_STORAGE_KEY = "figma-token";
845
+ function createRemoteReviewFigmaImageStore({
846
+ client,
847
+ uploadEndpoint,
848
+ table = DEFAULT_REVIEW_FIGMA_REMOTE_IMAGES_TABLE,
849
+ fetch: fetchOption,
850
+ token,
851
+ clientRender = true,
852
+ uploadTimeoutMs = DEFAULT_REVIEW_FIGMA_REMOTE_UPLOAD_TIMEOUT_MS
853
+ }) {
854
+ const listImages = async (target) => {
855
+ const targetKey = getReviewFigmaImageTargetKey(target);
856
+ const { data, error } = await client.from(table).select("*").eq("project_id", target.projectId).eq("target_key", targetKey).order("sort_order", { ascending: true }).order("created_at", { ascending: true });
857
+ if (error) throwRemoteStoreError(error, "list Figma images");
858
+ return (data ?? []).map(rowToFigmaImage);
859
+ };
860
+ return {
861
+ listImages,
862
+ async addImage(input) {
863
+ const ref = parseReviewFigmaNodeRef(input.figmaUrl);
864
+ if (!ref) {
865
+ throw new Error("A Figma node link or fileKey->nodeId value is required.");
866
+ }
867
+ const id = createFigmaImageId();
868
+ const asset = await resolveFigmaImageAsset({
869
+ input,
870
+ token,
871
+ clientRender,
872
+ fetch: fetchOption
873
+ });
874
+ const uploaded = await uploadFigmaAsset({
875
+ asset,
876
+ endpoint: uploadEndpoint,
877
+ fetch: fetchOption,
878
+ imageId: id,
879
+ projectId: input.target.projectId,
880
+ timeoutMs: uploadTimeoutMs
881
+ });
882
+ const order = typeof input.order === "number" && Number.isFinite(input.order) ? input.order : await getNextImageOrder(listImages, input.target);
883
+ const now = (/* @__PURE__ */ new Date()).toISOString();
884
+ const row = createImageRow({
885
+ asset,
886
+ fileKey: ref.fileKey,
887
+ id,
888
+ imageUrl: uploaded.imageUrl,
889
+ input,
890
+ nodeId: ref.nodeId,
891
+ now,
892
+ order,
893
+ storageKey: uploaded.storageKey
894
+ });
895
+ const { data, error } = await client.from(table).insert(row).select("*").single();
896
+ if (error) throwRemoteStoreError(error, "insert Figma image");
897
+ return rowToFigmaImage(data);
898
+ },
899
+ async updateImage(id, patch) {
900
+ const { data, error } = await client.from(table).update(createUpdatePatch(patch)).eq("id", id).select("*").single();
901
+ if (error) throwRemoteStoreError(error, "update Figma image");
902
+ return rowToFigmaImage(data);
903
+ },
904
+ async reorderImages(input) {
905
+ const current = await listImages(input.target);
906
+ const currentIds = new Set(current.map((image) => image.id));
907
+ const nextIds = [
908
+ ...input.imageIds.filter((id) => currentIds.has(id)),
909
+ ...current.map((image) => image.id).filter((id) => !input.imageIds.includes(id))
910
+ ];
911
+ const targetKey = getReviewFigmaImageTargetKey(input.target);
912
+ const updatedAt = (/* @__PURE__ */ new Date()).toISOString();
913
+ const results = await Promise.all(
914
+ nextIds.map(
915
+ (id, index) => client.from(table).update({ sort_order: index, updated_at: updatedAt }).eq("id", id).eq("project_id", input.target.projectId).eq("target_key", targetKey)
916
+ )
917
+ );
918
+ const failed = results.find((result) => result.error);
919
+ if (failed?.error) throwRemoteStoreError(failed.error, "reorder Figma images");
920
+ return listImages(input.target);
921
+ },
922
+ async deleteImage(id) {
923
+ const { error } = await client.from(table).delete().eq("id", id);
924
+ if (error) throwRemoteStoreError(error, "delete Figma image");
925
+ }
926
+ };
927
+ }
928
+ async function resolveFigmaImageAsset({
929
+ input,
930
+ token,
931
+ clientRender,
932
+ fetch
933
+ }) {
934
+ if (input.asset) return input.asset;
935
+ if (!clientRender) {
936
+ throw new Error("Figma image asset is required when clientRender is disabled.");
937
+ }
938
+ const renderOptions = clientRender === true ? {} : clientRender;
939
+ const figmaToken = readRemoteFigmaToken(renderOptions.token ?? token);
940
+ if (!figmaToken) throw new Error("Figma token is required.");
941
+ return createReviewFigmaClientRenderedAsset({
942
+ ...renderOptions,
943
+ fetch,
944
+ figmaUrl: input.figmaUrl,
945
+ token: figmaToken
946
+ });
947
+ }
948
+ async function uploadFigmaAsset({
949
+ asset,
950
+ endpoint,
951
+ fetch: fetchOption,
952
+ imageId,
953
+ projectId,
954
+ timeoutMs
955
+ }) {
956
+ const requestFetch = fetchOption ?? globalThis.fetch;
957
+ if (!requestFetch) throw new Error("Figma asset upload requires fetch.");
958
+ const { blob, mimeType } = decodeAssetDataUrl(asset);
959
+ const response = await withTimeout2(
960
+ requestFetch(
961
+ createAssetUploadUrl(endpoint, {
962
+ imageFormat: asset.imageFormat,
963
+ imageId,
964
+ projectId
965
+ }),
966
+ {
967
+ method: "POST",
968
+ headers: {
969
+ "Content-Type": mimeType
970
+ },
971
+ body: blob
972
+ }
973
+ ),
974
+ timeoutMs,
975
+ "Figma asset upload timed out."
976
+ );
977
+ const data = await response.json().catch(() => null);
978
+ if (!response.ok) {
979
+ throw new Error(
980
+ readString(data?.error) || `Figma asset upload failed with ${response.status}`
981
+ );
982
+ }
983
+ const storageKey = readString(data?.storageKey) ?? readString(data?.r2Key);
984
+ const imageUrl = readString(data?.imageUrl) ?? readString(data?.publicUrl);
985
+ if (!storageKey || !imageUrl) {
986
+ throw new Error("Figma asset upload response is missing image URL.");
987
+ }
988
+ return { storageKey, imageUrl };
989
+ }
990
+ function createAssetUploadUrl(endpoint, params) {
991
+ const base = typeof window !== "undefined" ? window.location.origin : "http://localhost";
992
+ const url = new URL(endpoint, base);
993
+ url.searchParams.set("projectId", params.projectId);
994
+ url.searchParams.set("imageId", params.imageId);
995
+ url.searchParams.set("imageFormat", params.imageFormat);
996
+ return url.toString();
997
+ }
998
+ function createImageRow({
999
+ asset,
1000
+ fileKey,
1001
+ id,
1002
+ imageUrl,
1003
+ input,
1004
+ nodeId,
1005
+ now,
1006
+ order,
1007
+ storageKey
1008
+ }) {
1009
+ const target = input.target;
1010
+ const targetKey = getReviewFigmaImageTargetKey(target);
1011
+ return {
1012
+ id,
1013
+ project_id: target.projectId,
1014
+ target_key: targetKey,
1015
+ target,
1016
+ target_type: target.type,
1017
+ page_url: target.type === "route" ? target.pageUrl : null,
1018
+ viewport_label: target.type === "route" ? target.viewport?.label ?? null : null,
1019
+ viewport_width: target.type === "route" ? target.viewport?.width ?? null : null,
1020
+ viewport_height: target.type === "route" ? target.viewport?.height ?? null : null,
1021
+ viewport_scope: target.type === "route" ? target.viewport?.scope ?? null : null,
1022
+ slot: target.type === "route" ? target.slot ?? null : null,
1023
+ figma_url: input.figmaUrl,
1024
+ file_key: fileKey,
1025
+ node_id: nodeId,
1026
+ image_url: imageUrl,
1027
+ image_format: asset.imageFormat,
1028
+ mime_type: asset.mimeType,
1029
+ storage_key: storageKey,
1030
+ label: normalizeOptionalText(input.label) ?? null,
1031
+ sort_order: order,
1032
+ width: asset.width ?? null,
1033
+ height: asset.height ?? null,
1034
+ byte_size: asset.byteSize ?? null,
1035
+ created_at: now,
1036
+ updated_at: now
1037
+ };
1038
+ }
1039
+ function createUpdatePatch(patch) {
1040
+ return {
1041
+ ...patch.label !== void 0 ? { label: normalizeOptionalText(patch.label) ?? null } : {},
1042
+ ...typeof patch.order === "number" && Number.isFinite(patch.order) ? { sort_order: patch.order } : {},
1043
+ updated_at: (/* @__PURE__ */ new Date()).toISOString()
1044
+ };
1045
+ }
1046
+ async function getNextImageOrder(listImages, target) {
1047
+ const images = await listImages(target);
1048
+ return images.length ? Math.max(...images.map((image) => image.order)) + 1 : 0;
1049
+ }
1050
+ function rowToFigmaImage(row) {
1051
+ return {
1052
+ id: row.id,
1053
+ projectId: row.project_id,
1054
+ target: row.target,
1055
+ figmaUrl: row.figma_url,
1056
+ fileKey: row.file_key,
1057
+ nodeId: row.node_id,
1058
+ imageUrl: row.image_url,
1059
+ imageFormat: row.image_format,
1060
+ mimeType: row.mime_type,
1061
+ label: row.label ?? void 0,
1062
+ order: row.sort_order,
1063
+ storageKey: row.storage_key,
1064
+ width: row.width ?? void 0,
1065
+ height: row.height ?? void 0,
1066
+ byteSize: row.byte_size ?? void 0,
1067
+ createdAt: row.created_at,
1068
+ updatedAt: row.updated_at
1069
+ };
1070
+ }
1071
+ function decodeAssetDataUrl(asset) {
1072
+ const match = asset.dataUrl.match(/^data:([^;,]+);base64,(.*)$/);
1073
+ if (!match) throw new Error("Valid Figma image asset data URL is required.");
1074
+ const mimeType = match[1]?.trim();
1075
+ if (!mimeType || mimeType !== asset.mimeType) {
1076
+ throw new Error("Figma image asset MIME type mismatch.");
1077
+ }
1078
+ const binary = globalThis.atob(match[2] ?? "");
1079
+ const bytes = new Uint8Array(binary.length);
1080
+ for (let index = 0; index < binary.length; index += 1) {
1081
+ bytes[index] = binary.charCodeAt(index);
1082
+ }
1083
+ return {
1084
+ blob: new Blob([bytes], { type: mimeType }),
1085
+ mimeType
1086
+ };
1087
+ }
1088
+ async function withTimeout2(promise, timeoutMs, message) {
1089
+ let timeoutId;
1090
+ try {
1091
+ return await Promise.race([
1092
+ promise,
1093
+ new Promise((_, reject) => {
1094
+ timeoutId = setTimeout(() => reject(new Error(message)), timeoutMs);
1095
+ })
1096
+ ]);
1097
+ } finally {
1098
+ if (timeoutId) clearTimeout(timeoutId);
1099
+ }
1100
+ }
1101
+ function readRemoteFigmaToken(provider) {
1102
+ const token = typeof provider === "function" ? provider() : provider;
1103
+ if (typeof token === "string" && token.trim()) return token.trim();
1104
+ return readStoredFigmaToken();
1105
+ }
1106
+ function readStoredFigmaToken() {
1107
+ if (typeof window === "undefined") return "";
1108
+ try {
1109
+ return window.localStorage.getItem(FIGMA_TOKEN_STORAGE_KEY)?.trim() || "";
1110
+ } catch {
1111
+ return "";
1112
+ }
1113
+ }
1114
+ function createFigmaImageId() {
1115
+ const id = globalThis.crypto?.randomUUID?.() ?? Math.random().toString(36).slice(2);
1116
+ return `figma_${id.replace(/[^A-Za-z0-9_-]+/g, "-")}`;
1117
+ }
1118
+ function normalizeOptionalText(value) {
1119
+ if (typeof value !== "string") return void 0;
1120
+ return value.trim() || void 0;
1121
+ }
1122
+ function readString(value) {
1123
+ return typeof value === "string" && value.trim() ? value.trim() : null;
1124
+ }
1125
+ function throwRemoteStoreError(error, action) {
1126
+ const source = error;
1127
+ const message = readString(source.message) ?? `Remote Figma image ${action} failed.`;
1128
+ const code = readString(source.code);
1129
+ const details = readString(source.details);
1130
+ const hint = readString(source.hint);
1131
+ const suffix = [code, details, hint].filter(Boolean).join(" / ");
1132
+ throw new Error(suffix ? `${message} (${suffix})` : message);
1133
+ }
1134
+
802
1135
  // src/figma/image.snapshot.ts
803
1136
  function createReviewFigmaImagesSnapshot(images, options = {}) {
804
1137
  const targetKeys = options.targets?.length ? new Set(options.targets.map(getReviewFigmaImageTargetKey)) : null;
@@ -5381,6 +5714,7 @@ function createNoopController() {
5381
5714
  0 && (module.exports = {
5382
5715
  DEFAULT_REVIEW_FIGMA_IMAGE_FORMAT,
5383
5716
  DEFAULT_REVIEW_FIGMA_IMAGE_STORE_ENDPOINT,
5717
+ DEFAULT_REVIEW_FIGMA_REMOTE_IMAGES_TABLE,
5384
5718
  DEFAULT_REVIEW_FIGMA_TOKEN_ENV_KEY,
5385
5719
  DEFAULT_REVIEW_VIEWPORTS,
5386
5720
  FIGMA_NODE_REF_SEPARATOR,
@@ -5388,6 +5722,8 @@ function createNoopController() {
5388
5722
  REVIEW_WORKFLOW_STATUS_OPTIONS,
5389
5723
  ReviewFigmaTokenError,
5390
5724
  collectReviewFigmaReleaseSnapshot,
5725
+ createEndpointReviewFigmaImageStore,
5726
+ createRemoteReviewFigmaImageStore,
5391
5727
  createReviewFigmaFrameUrl,
5392
5728
  createReviewFigmaImageStoreClient,
5393
5729
  createReviewFigmaImagesSnapshot,