@designfever/web-review-kit 0.7.0 → 0.7.2

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/index.d.cts CHANGED
@@ -16,9 +16,11 @@ declare const REVIEW_WORKFLOW_STATUS_OPTIONS: Array<{
16
16
  declare function normalizeReviewItemStatus(status: ReviewItemStatus | undefined): ReviewWorkflowStatus;
17
17
 
18
18
  declare const DEFAULT_REVIEW_FIGMA_IMAGE_STORE_ENDPOINT = "/__dfwr/figma-images";
19
+ type ReviewFigmaImageTokenProvider = string | null | undefined | (() => string | null | undefined);
19
20
  type ReviewFigmaImageStoreClientOptions = {
20
21
  endpoint?: string;
21
22
  fetch?: typeof fetch;
23
+ token?: ReviewFigmaImageTokenProvider;
22
24
  clientRender?: boolean | ReviewFigmaImageClientRenderOptions;
23
25
  };
24
26
  type ReviewFigmaImageClientRenderOptions = {
@@ -35,6 +37,71 @@ declare function createReviewFigmaImageStoreClient(options?: ReviewFigmaImageSto
35
37
  declare function getReviewFigmaImageTargetKey(target: ReviewFigmaImageTarget): string;
36
38
  declare function getReviewFigmaImageMimeType(format: ReviewFigmaImageFormat): "image/webp" | "image/png" | "image/jpeg";
37
39
 
40
+ declare const DEFAULT_REVIEW_FIGMA_REMOTE_IMAGES_TABLE = "review_figma_images";
41
+ type ReviewFigmaRemoteTokenProvider = string | null | undefined | (() => string | null | undefined);
42
+ type ReviewFigmaRemoteImageRow = {
43
+ id: string;
44
+ project_id: string;
45
+ target_key: string;
46
+ target: ReviewFigmaImageTarget;
47
+ target_type: 'route' | 'figma-node';
48
+ page_url: string | null;
49
+ viewport_label: string | null;
50
+ viewport_width: number | null;
51
+ viewport_height: number | null;
52
+ viewport_scope: 'mobile' | 'tablet' | 'desktop' | 'wide' | null;
53
+ slot: string | null;
54
+ figma_url: string;
55
+ file_key: string;
56
+ node_id: string;
57
+ image_url: string;
58
+ image_format: ReviewFigmaImageFormat;
59
+ mime_type: string;
60
+ storage_key: string;
61
+ label: string | null;
62
+ sort_order: number;
63
+ width: number | null;
64
+ height: number | null;
65
+ byte_size: number | null;
66
+ created_at: string;
67
+ updated_at: string;
68
+ };
69
+ type ReviewFigmaRemoteDbResult<T = unknown> = {
70
+ data: T | null;
71
+ error: unknown;
72
+ };
73
+ type ReviewFigmaRemoteDbQuery<T = unknown> = PromiseLike<ReviewFigmaRemoteDbResult<T>> & {
74
+ delete(): ReviewFigmaRemoteDbQuery<T>;
75
+ eq(column: string, value: unknown): ReviewFigmaRemoteDbQuery<T>;
76
+ insert(value: unknown): ReviewFigmaRemoteDbQuery<T>;
77
+ order(column: string, options?: {
78
+ ascending?: boolean;
79
+ }): ReviewFigmaRemoteDbQuery<T>;
80
+ select(columns?: string): ReviewFigmaRemoteDbQuery<T>;
81
+ single(): PromiseLike<ReviewFigmaRemoteDbResult<T>>;
82
+ update(value: unknown): ReviewFigmaRemoteDbQuery<T>;
83
+ };
84
+ type ReviewFigmaRemoteDbClient = {
85
+ from(table: string): ReviewFigmaRemoteDbQuery;
86
+ };
87
+ type ReviewFigmaRemoteAssetUploadResponse = {
88
+ storageKey?: unknown;
89
+ r2Key?: unknown;
90
+ imageUrl?: unknown;
91
+ publicUrl?: unknown;
92
+ error?: unknown;
93
+ };
94
+ type RemoteReviewFigmaImageStoreOptions = {
95
+ client: ReviewFigmaRemoteDbClient;
96
+ uploadEndpoint: string;
97
+ table?: string;
98
+ fetch?: typeof fetch;
99
+ token?: ReviewFigmaRemoteTokenProvider;
100
+ clientRender?: boolean | ReviewFigmaImageClientRenderOptions;
101
+ uploadTimeoutMs?: number;
102
+ };
103
+ declare function createRemoteReviewFigmaImageStore({ client, uploadEndpoint, table, fetch: fetchOption, token, clientRender, uploadTimeoutMs, }: RemoteReviewFigmaImageStoreOptions): ReviewFigmaImageStore;
104
+
38
105
  /** Creates the vanilla runtime controller that mounts review overlays on a target page. */
39
106
  declare function createWebReviewKit(options: WebReviewKitOptions): WebReviewKitController;
40
107
 
@@ -51,4 +118,4 @@ declare function getReviewItemScopeLabel(item: ReviewItem, presets?: ReviewViewp
51
118
  /** Adds scope-aware display labels to review items without mutating them. */
52
119
  declare function getNumberedReviewItems(items: ReviewItem[], presets?: ReviewViewportPreset[]): NumberedReviewItem[];
53
120
 
54
- export { DEFAULT_REVIEW_FIGMA_IMAGE_STORE_ENDPOINT, DEFAULT_REVIEW_VIEWPORTS, LocalAdapterOptions, NumberedReviewItem, REVIEW_WORKFLOW_STATUS_OPTIONS, type ReviewFigmaImageClientRenderOptions, ReviewFigmaImageFormat, ReviewFigmaImageStore, type ReviewFigmaImageStoreClientOptions, ReviewFigmaImageTarget, ReviewItem, ReviewItemScope, ReviewItemStatus, ReviewViewportPreset, ReviewWorkflowStatus, SupabaseReviewAdapterOptions, ViewportSize, WebReviewKitAdapter, WebReviewKitController, WebReviewKitOptions, createReviewFigmaImageStoreClient, createWebReviewKit, findReviewViewportPreset, getNumberedReviewItems, getReviewFigmaImageMimeType, getReviewFigmaImageTargetKey, getReviewItemScope, getReviewItemScopeLabel, getReviewViewportScope, localAdapter, normalizeReviewItemStatus, supabaseAdapter };
121
+ export { DEFAULT_REVIEW_FIGMA_IMAGE_STORE_ENDPOINT, DEFAULT_REVIEW_FIGMA_REMOTE_IMAGES_TABLE, DEFAULT_REVIEW_VIEWPORTS, LocalAdapterOptions, NumberedReviewItem, REVIEW_WORKFLOW_STATUS_OPTIONS, type RemoteReviewFigmaImageStoreOptions, type ReviewFigmaImageClientRenderOptions, ReviewFigmaImageFormat, ReviewFigmaImageStore, type ReviewFigmaImageStoreClientOptions, ReviewFigmaImageTarget, type ReviewFigmaRemoteAssetUploadResponse, type ReviewFigmaRemoteDbClient, type ReviewFigmaRemoteImageRow, ReviewItem, ReviewItemScope, ReviewItemStatus, ReviewViewportPreset, ReviewWorkflowStatus, SupabaseReviewAdapterOptions, ViewportSize, WebReviewKitAdapter, WebReviewKitController, WebReviewKitOptions, createRemoteReviewFigmaImageStore, createReviewFigmaImageStoreClient, createWebReviewKit, findReviewViewportPreset, getNumberedReviewItems, getReviewFigmaImageMimeType, getReviewFigmaImageTargetKey, getReviewItemScope, getReviewItemScopeLabel, getReviewViewportScope, localAdapter, normalizeReviewItemStatus, supabaseAdapter };
package/dist/index.d.ts CHANGED
@@ -16,9 +16,11 @@ declare const REVIEW_WORKFLOW_STATUS_OPTIONS: Array<{
16
16
  declare function normalizeReviewItemStatus(status: ReviewItemStatus | undefined): ReviewWorkflowStatus;
17
17
 
18
18
  declare const DEFAULT_REVIEW_FIGMA_IMAGE_STORE_ENDPOINT = "/__dfwr/figma-images";
19
+ type ReviewFigmaImageTokenProvider = string | null | undefined | (() => string | null | undefined);
19
20
  type ReviewFigmaImageStoreClientOptions = {
20
21
  endpoint?: string;
21
22
  fetch?: typeof fetch;
23
+ token?: ReviewFigmaImageTokenProvider;
22
24
  clientRender?: boolean | ReviewFigmaImageClientRenderOptions;
23
25
  };
24
26
  type ReviewFigmaImageClientRenderOptions = {
@@ -35,6 +37,71 @@ declare function createReviewFigmaImageStoreClient(options?: ReviewFigmaImageSto
35
37
  declare function getReviewFigmaImageTargetKey(target: ReviewFigmaImageTarget): string;
36
38
  declare function getReviewFigmaImageMimeType(format: ReviewFigmaImageFormat): "image/webp" | "image/png" | "image/jpeg";
37
39
 
40
+ declare const DEFAULT_REVIEW_FIGMA_REMOTE_IMAGES_TABLE = "review_figma_images";
41
+ type ReviewFigmaRemoteTokenProvider = string | null | undefined | (() => string | null | undefined);
42
+ type ReviewFigmaRemoteImageRow = {
43
+ id: string;
44
+ project_id: string;
45
+ target_key: string;
46
+ target: ReviewFigmaImageTarget;
47
+ target_type: 'route' | 'figma-node';
48
+ page_url: string | null;
49
+ viewport_label: string | null;
50
+ viewport_width: number | null;
51
+ viewport_height: number | null;
52
+ viewport_scope: 'mobile' | 'tablet' | 'desktop' | 'wide' | null;
53
+ slot: string | null;
54
+ figma_url: string;
55
+ file_key: string;
56
+ node_id: string;
57
+ image_url: string;
58
+ image_format: ReviewFigmaImageFormat;
59
+ mime_type: string;
60
+ storage_key: string;
61
+ label: string | null;
62
+ sort_order: number;
63
+ width: number | null;
64
+ height: number | null;
65
+ byte_size: number | null;
66
+ created_at: string;
67
+ updated_at: string;
68
+ };
69
+ type ReviewFigmaRemoteDbResult<T = unknown> = {
70
+ data: T | null;
71
+ error: unknown;
72
+ };
73
+ type ReviewFigmaRemoteDbQuery<T = unknown> = PromiseLike<ReviewFigmaRemoteDbResult<T>> & {
74
+ delete(): ReviewFigmaRemoteDbQuery<T>;
75
+ eq(column: string, value: unknown): ReviewFigmaRemoteDbQuery<T>;
76
+ insert(value: unknown): ReviewFigmaRemoteDbQuery<T>;
77
+ order(column: string, options?: {
78
+ ascending?: boolean;
79
+ }): ReviewFigmaRemoteDbQuery<T>;
80
+ select(columns?: string): ReviewFigmaRemoteDbQuery<T>;
81
+ single(): PromiseLike<ReviewFigmaRemoteDbResult<T>>;
82
+ update(value: unknown): ReviewFigmaRemoteDbQuery<T>;
83
+ };
84
+ type ReviewFigmaRemoteDbClient = {
85
+ from(table: string): ReviewFigmaRemoteDbQuery;
86
+ };
87
+ type ReviewFigmaRemoteAssetUploadResponse = {
88
+ storageKey?: unknown;
89
+ r2Key?: unknown;
90
+ imageUrl?: unknown;
91
+ publicUrl?: unknown;
92
+ error?: unknown;
93
+ };
94
+ type RemoteReviewFigmaImageStoreOptions = {
95
+ client: ReviewFigmaRemoteDbClient;
96
+ uploadEndpoint: string;
97
+ table?: string;
98
+ fetch?: typeof fetch;
99
+ token?: ReviewFigmaRemoteTokenProvider;
100
+ clientRender?: boolean | ReviewFigmaImageClientRenderOptions;
101
+ uploadTimeoutMs?: number;
102
+ };
103
+ declare function createRemoteReviewFigmaImageStore({ client, uploadEndpoint, table, fetch: fetchOption, token, clientRender, uploadTimeoutMs, }: RemoteReviewFigmaImageStoreOptions): ReviewFigmaImageStore;
104
+
38
105
  /** Creates the vanilla runtime controller that mounts review overlays on a target page. */
39
106
  declare function createWebReviewKit(options: WebReviewKitOptions): WebReviewKitController;
40
107
 
@@ -51,4 +118,4 @@ declare function getReviewItemScopeLabel(item: ReviewItem, presets?: ReviewViewp
51
118
  /** Adds scope-aware display labels to review items without mutating them. */
52
119
  declare function getNumberedReviewItems(items: ReviewItem[], presets?: ReviewViewportPreset[]): NumberedReviewItem[];
53
120
 
54
- export { DEFAULT_REVIEW_FIGMA_IMAGE_STORE_ENDPOINT, DEFAULT_REVIEW_VIEWPORTS, LocalAdapterOptions, NumberedReviewItem, REVIEW_WORKFLOW_STATUS_OPTIONS, type ReviewFigmaImageClientRenderOptions, ReviewFigmaImageFormat, ReviewFigmaImageStore, type ReviewFigmaImageStoreClientOptions, ReviewFigmaImageTarget, ReviewItem, ReviewItemScope, ReviewItemStatus, ReviewViewportPreset, ReviewWorkflowStatus, SupabaseReviewAdapterOptions, ViewportSize, WebReviewKitAdapter, WebReviewKitController, WebReviewKitOptions, createReviewFigmaImageStoreClient, createWebReviewKit, findReviewViewportPreset, getNumberedReviewItems, getReviewFigmaImageMimeType, getReviewFigmaImageTargetKey, getReviewItemScope, getReviewItemScopeLabel, getReviewViewportScope, localAdapter, normalizeReviewItemStatus, supabaseAdapter };
121
+ export { DEFAULT_REVIEW_FIGMA_IMAGE_STORE_ENDPOINT, DEFAULT_REVIEW_FIGMA_REMOTE_IMAGES_TABLE, DEFAULT_REVIEW_VIEWPORTS, LocalAdapterOptions, NumberedReviewItem, REVIEW_WORKFLOW_STATUS_OPTIONS, type RemoteReviewFigmaImageStoreOptions, type ReviewFigmaImageClientRenderOptions, ReviewFigmaImageFormat, ReviewFigmaImageStore, type ReviewFigmaImageStoreClientOptions, ReviewFigmaImageTarget, type ReviewFigmaRemoteAssetUploadResponse, type ReviewFigmaRemoteDbClient, type ReviewFigmaRemoteImageRow, ReviewItem, ReviewItemScope, ReviewItemStatus, ReviewViewportPreset, ReviewWorkflowStatus, SupabaseReviewAdapterOptions, ViewportSize, WebReviewKitAdapter, WebReviewKitController, WebReviewKitOptions, createRemoteReviewFigmaImageStore, createReviewFigmaImageStoreClient, createWebReviewKit, findReviewViewportPreset, getNumberedReviewItems, getReviewFigmaImageMimeType, getReviewFigmaImageTargetKey, getReviewItemScope, getReviewItemScopeLabel, getReviewViewportScope, localAdapter, normalizeReviewItemStatus, supabaseAdapter };
package/dist/index.js CHANGED
@@ -18,6 +18,7 @@ import {
18
18
  REVIEW_FIGMA_TOKEN_MISSING_CODE,
19
19
  ReviewFigmaTokenError,
20
20
  collectReviewFigmaReleaseSnapshot,
21
+ createReviewFigmaClientRenderedAsset,
21
22
  createReviewFigmaFrameUrl,
22
23
  createReviewFigmaImageStoreClient,
23
24
  createReviewFigmaImagesSnapshot,
@@ -30,7 +31,7 @@ import {
30
31
  readReviewFigmaToken,
31
32
  requireReviewFigmaNodeRef,
32
33
  requireReviewFigmaToken
33
- } from "./chunk-AB5B6O77.js";
34
+ } from "./chunk-K26WLRRP.js";
34
35
 
35
36
  // src/adapters/supabase.ts
36
37
  var DEFAULT_SUPABASE_REVIEW_TABLE = "review_items";
@@ -256,9 +257,304 @@ function createSupabaseReviewItemId() {
256
257
  }
257
258
  return `review-${Date.now()}-${Math.random().toString(36).slice(2, 10)}`;
258
259
  }
260
+
261
+ // src/figma/remote.image.store.ts
262
+ var DEFAULT_REVIEW_FIGMA_REMOTE_IMAGES_TABLE = "review_figma_images";
263
+ var DEFAULT_REVIEW_FIGMA_REMOTE_UPLOAD_TIMEOUT_MS = 3e4;
264
+ var FIGMA_TOKEN_STORAGE_KEY = "figma-token";
265
+ function createRemoteReviewFigmaImageStore({
266
+ client,
267
+ uploadEndpoint,
268
+ table = DEFAULT_REVIEW_FIGMA_REMOTE_IMAGES_TABLE,
269
+ fetch: fetchOption,
270
+ token,
271
+ clientRender = true,
272
+ uploadTimeoutMs = DEFAULT_REVIEW_FIGMA_REMOTE_UPLOAD_TIMEOUT_MS
273
+ }) {
274
+ const listImages = async (target) => {
275
+ const targetKey = getReviewFigmaImageTargetKey(target);
276
+ 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 });
277
+ if (error) throwRemoteStoreError(error, "list Figma images");
278
+ return (data ?? []).map(rowToFigmaImage);
279
+ };
280
+ return {
281
+ listImages,
282
+ async addImage(input) {
283
+ const ref = parseReviewFigmaNodeRef(input.figmaUrl);
284
+ if (!ref) {
285
+ throw new Error("A Figma node link or fileKey->nodeId value is required.");
286
+ }
287
+ const id = createFigmaImageId();
288
+ const asset = await resolveFigmaImageAsset({
289
+ input,
290
+ token,
291
+ clientRender,
292
+ fetch: fetchOption
293
+ });
294
+ const uploaded = await uploadFigmaAsset({
295
+ asset,
296
+ endpoint: uploadEndpoint,
297
+ fetch: fetchOption,
298
+ imageId: id,
299
+ projectId: input.target.projectId,
300
+ timeoutMs: uploadTimeoutMs
301
+ });
302
+ const order = typeof input.order === "number" && Number.isFinite(input.order) ? input.order : await getNextImageOrder(listImages, input.target);
303
+ const now = (/* @__PURE__ */ new Date()).toISOString();
304
+ const row = createImageRow({
305
+ asset,
306
+ fileKey: ref.fileKey,
307
+ id,
308
+ imageUrl: uploaded.imageUrl,
309
+ input,
310
+ nodeId: ref.nodeId,
311
+ now,
312
+ order,
313
+ storageKey: uploaded.storageKey
314
+ });
315
+ const { data, error } = await client.from(table).insert(row).select("*").single();
316
+ if (error) throwRemoteStoreError(error, "insert Figma image");
317
+ return rowToFigmaImage(data);
318
+ },
319
+ async updateImage(id, patch) {
320
+ const { data, error } = await client.from(table).update(createUpdatePatch(patch)).eq("id", id).select("*").single();
321
+ if (error) throwRemoteStoreError(error, "update Figma image");
322
+ return rowToFigmaImage(data);
323
+ },
324
+ async reorderImages(input) {
325
+ const current = await listImages(input.target);
326
+ const currentIds = new Set(current.map((image) => image.id));
327
+ const nextIds = [
328
+ ...input.imageIds.filter((id) => currentIds.has(id)),
329
+ ...current.map((image) => image.id).filter((id) => !input.imageIds.includes(id))
330
+ ];
331
+ const targetKey = getReviewFigmaImageTargetKey(input.target);
332
+ const updatedAt = (/* @__PURE__ */ new Date()).toISOString();
333
+ const results = await Promise.all(
334
+ nextIds.map(
335
+ (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)
336
+ )
337
+ );
338
+ const failed = results.find((result) => result.error);
339
+ if (failed?.error) throwRemoteStoreError(failed.error, "reorder Figma images");
340
+ return listImages(input.target);
341
+ },
342
+ async deleteImage(id) {
343
+ const { error } = await client.from(table).delete().eq("id", id);
344
+ if (error) throwRemoteStoreError(error, "delete Figma image");
345
+ }
346
+ };
347
+ }
348
+ async function resolveFigmaImageAsset({
349
+ input,
350
+ token,
351
+ clientRender,
352
+ fetch
353
+ }) {
354
+ if (input.asset) return input.asset;
355
+ if (!clientRender) {
356
+ throw new Error("Figma image asset is required when clientRender is disabled.");
357
+ }
358
+ const renderOptions = clientRender === true ? {} : clientRender;
359
+ const figmaToken = readRemoteFigmaToken(renderOptions.token ?? token);
360
+ if (!figmaToken) throw new Error("Figma token is required.");
361
+ return createReviewFigmaClientRenderedAsset({
362
+ ...renderOptions,
363
+ fetch,
364
+ figmaUrl: input.figmaUrl,
365
+ token: figmaToken
366
+ });
367
+ }
368
+ async function uploadFigmaAsset({
369
+ asset,
370
+ endpoint,
371
+ fetch: fetchOption,
372
+ imageId,
373
+ projectId,
374
+ timeoutMs
375
+ }) {
376
+ const requestFetch = fetchOption ?? globalThis.fetch;
377
+ if (!requestFetch) throw new Error("Figma asset upload requires fetch.");
378
+ const { blob, mimeType } = decodeAssetDataUrl(asset);
379
+ const response = await withTimeout(
380
+ requestFetch(
381
+ createAssetUploadUrl(endpoint, {
382
+ imageFormat: asset.imageFormat,
383
+ imageId,
384
+ projectId
385
+ }),
386
+ {
387
+ method: "POST",
388
+ headers: {
389
+ "Content-Type": mimeType
390
+ },
391
+ body: blob
392
+ }
393
+ ),
394
+ timeoutMs,
395
+ "Figma asset upload timed out."
396
+ );
397
+ const data = await response.json().catch(() => null);
398
+ if (!response.ok) {
399
+ throw new Error(
400
+ readString(data?.error) || `Figma asset upload failed with ${response.status}`
401
+ );
402
+ }
403
+ const storageKey = readString(data?.storageKey) ?? readString(data?.r2Key);
404
+ const imageUrl = readString(data?.imageUrl) ?? readString(data?.publicUrl);
405
+ if (!storageKey || !imageUrl) {
406
+ throw new Error("Figma asset upload response is missing image URL.");
407
+ }
408
+ return { storageKey, imageUrl };
409
+ }
410
+ function createAssetUploadUrl(endpoint, params) {
411
+ const base = typeof window !== "undefined" ? window.location.origin : "http://localhost";
412
+ const url = new URL(endpoint, base);
413
+ url.searchParams.set("projectId", params.projectId);
414
+ url.searchParams.set("imageId", params.imageId);
415
+ url.searchParams.set("imageFormat", params.imageFormat);
416
+ return url.toString();
417
+ }
418
+ function createImageRow({
419
+ asset,
420
+ fileKey,
421
+ id,
422
+ imageUrl,
423
+ input,
424
+ nodeId,
425
+ now,
426
+ order,
427
+ storageKey
428
+ }) {
429
+ const target = input.target;
430
+ const targetKey = getReviewFigmaImageTargetKey(target);
431
+ return {
432
+ id,
433
+ project_id: target.projectId,
434
+ target_key: targetKey,
435
+ target,
436
+ target_type: target.type,
437
+ page_url: target.type === "route" ? target.pageUrl : null,
438
+ viewport_label: target.type === "route" ? target.viewport?.label ?? null : null,
439
+ viewport_width: target.type === "route" ? target.viewport?.width ?? null : null,
440
+ viewport_height: target.type === "route" ? target.viewport?.height ?? null : null,
441
+ viewport_scope: target.type === "route" ? target.viewport?.scope ?? null : null,
442
+ slot: target.type === "route" ? target.slot ?? null : null,
443
+ figma_url: input.figmaUrl,
444
+ file_key: fileKey,
445
+ node_id: nodeId,
446
+ image_url: imageUrl,
447
+ image_format: asset.imageFormat,
448
+ mime_type: asset.mimeType,
449
+ storage_key: storageKey,
450
+ label: normalizeOptionalText(input.label) ?? null,
451
+ sort_order: order,
452
+ width: asset.width ?? null,
453
+ height: asset.height ?? null,
454
+ byte_size: asset.byteSize ?? null,
455
+ created_at: now,
456
+ updated_at: now
457
+ };
458
+ }
459
+ function createUpdatePatch(patch) {
460
+ return {
461
+ ...patch.label !== void 0 ? { label: normalizeOptionalText(patch.label) ?? null } : {},
462
+ ...typeof patch.order === "number" && Number.isFinite(patch.order) ? { sort_order: patch.order } : {},
463
+ updated_at: (/* @__PURE__ */ new Date()).toISOString()
464
+ };
465
+ }
466
+ async function getNextImageOrder(listImages, target) {
467
+ const images = await listImages(target);
468
+ return images.length ? Math.max(...images.map((image) => image.order)) + 1 : 0;
469
+ }
470
+ function rowToFigmaImage(row) {
471
+ return {
472
+ id: row.id,
473
+ projectId: row.project_id,
474
+ target: row.target,
475
+ figmaUrl: row.figma_url,
476
+ fileKey: row.file_key,
477
+ nodeId: row.node_id,
478
+ imageUrl: row.image_url,
479
+ imageFormat: row.image_format,
480
+ mimeType: row.mime_type,
481
+ label: row.label ?? void 0,
482
+ order: row.sort_order,
483
+ storageKey: row.storage_key,
484
+ width: row.width ?? void 0,
485
+ height: row.height ?? void 0,
486
+ byteSize: row.byte_size ?? void 0,
487
+ createdAt: row.created_at,
488
+ updatedAt: row.updated_at
489
+ };
490
+ }
491
+ function decodeAssetDataUrl(asset) {
492
+ const match = asset.dataUrl.match(/^data:([^;,]+);base64,(.*)$/);
493
+ if (!match) throw new Error("Valid Figma image asset data URL is required.");
494
+ const mimeType = match[1]?.trim();
495
+ if (!mimeType || mimeType !== asset.mimeType) {
496
+ throw new Error("Figma image asset MIME type mismatch.");
497
+ }
498
+ const binary = globalThis.atob(match[2] ?? "");
499
+ const bytes = new Uint8Array(binary.length);
500
+ for (let index = 0; index < binary.length; index += 1) {
501
+ bytes[index] = binary.charCodeAt(index);
502
+ }
503
+ return {
504
+ blob: new Blob([bytes], { type: mimeType }),
505
+ mimeType
506
+ };
507
+ }
508
+ async function withTimeout(promise, timeoutMs, message) {
509
+ let timeoutId;
510
+ try {
511
+ return await Promise.race([
512
+ promise,
513
+ new Promise((_, reject) => {
514
+ timeoutId = setTimeout(() => reject(new Error(message)), timeoutMs);
515
+ })
516
+ ]);
517
+ } finally {
518
+ if (timeoutId) clearTimeout(timeoutId);
519
+ }
520
+ }
521
+ function readRemoteFigmaToken(provider) {
522
+ const token = typeof provider === "function" ? provider() : provider;
523
+ if (typeof token === "string" && token.trim()) return token.trim();
524
+ return readStoredFigmaToken();
525
+ }
526
+ function readStoredFigmaToken() {
527
+ if (typeof window === "undefined") return "";
528
+ try {
529
+ return window.localStorage.getItem(FIGMA_TOKEN_STORAGE_KEY)?.trim() || "";
530
+ } catch {
531
+ return "";
532
+ }
533
+ }
534
+ function createFigmaImageId() {
535
+ const id = globalThis.crypto?.randomUUID?.() ?? Math.random().toString(36).slice(2);
536
+ return `figma_${id.replace(/[^A-Za-z0-9_-]+/g, "-")}`;
537
+ }
538
+ function normalizeOptionalText(value) {
539
+ if (typeof value !== "string") return void 0;
540
+ return value.trim() || void 0;
541
+ }
542
+ function readString(value) {
543
+ return typeof value === "string" && value.trim() ? value.trim() : null;
544
+ }
545
+ function throwRemoteStoreError(error, action) {
546
+ const source = error;
547
+ const message = readString(source.message) ?? `Remote Figma image ${action} failed.`;
548
+ const code = readString(source.code);
549
+ const details = readString(source.details);
550
+ const hint = readString(source.hint);
551
+ const suffix = [code, details, hint].filter(Boolean).join(" / ");
552
+ throw new Error(suffix ? `${message} (${suffix})` : message);
553
+ }
259
554
  export {
260
555
  DEFAULT_REVIEW_FIGMA_IMAGE_FORMAT,
261
556
  DEFAULT_REVIEW_FIGMA_IMAGE_STORE_ENDPOINT,
557
+ DEFAULT_REVIEW_FIGMA_REMOTE_IMAGES_TABLE,
262
558
  DEFAULT_REVIEW_FIGMA_TOKEN_ENV_KEY,
263
559
  DEFAULT_REVIEW_VIEWPORTS,
264
560
  FIGMA_NODE_REF_SEPARATOR,
@@ -266,6 +562,7 @@ export {
266
562
  REVIEW_WORKFLOW_STATUS_OPTIONS,
267
563
  ReviewFigmaTokenError,
268
564
  collectReviewFigmaReleaseSnapshot,
565
+ createRemoteReviewFigmaImageStore,
269
566
  createReviewFigmaFrameUrl,
270
567
  createReviewFigmaImageStoreClient,
271
568
  createReviewFigmaImagesSnapshot,
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/adapters/supabase.ts"],"sourcesContent":["import { normalizeReviewItemStatus } from '../status';\nimport type {\n ReviewItem,\n ReviewItemStatus,\n ReviewSource,\n SupabaseReviewAdapterOptions,\n WebReviewKitAdapter,\n} from '../types';\n\nconst DEFAULT_SUPABASE_REVIEW_TABLE = 'review_items';\nconst DEFAULT_SUPABASE_REVIEW_SOURCE = 'supabase';\nconst DEFAULT_SUPABASE_CREATE_REVIEW_ITEM_RPC = 'create_review_item';\n\ntype SupabaseReviewRow = {\n id: string;\n project_id: string;\n route_key: string;\n source: string;\n review_number?: number | null;\n status: string;\n item: unknown;\n created_at: string;\n updated_at: string;\n};\n\ntype SupabaseResponse<T> = {\n data: T | null;\n error: { message?: string; code?: string } | null;\n};\n\nexport function supabaseAdapter(\n options: SupabaseReviewAdapterOptions\n): WebReviewKitAdapter {\n const tableName = options.table ?? DEFAULT_SUPABASE_REVIEW_TABLE;\n const source = options.source ?? DEFAULT_SUPABASE_REVIEW_SOURCE;\n\n const fromTable = () => options.client.from(tableName);\n\n return {\n async get(id) {\n const row = await unwrapResponse<SupabaseReviewRow | null>(\n fromTable().select('*').eq('id', id).maybeSingle(),\n 'supabase get review item'\n );\n\n return row ? rowToReviewItem(row, options) : null;\n },\n\n async list(query) {\n let request = fromTable()\n .select('*')\n .eq('project_id', query.projectId)\n .eq('source', query.source ?? source);\n const routeKey = query.routeKey ?? query.normalizedPath;\n\n if (routeKey) {\n request = request.eq('route_key', routeKey);\n }\n if (query.status) {\n request = request.eq(\n 'status',\n normalizeReviewItemStatus(query.status)\n );\n }\n\n const rows = await unwrapResponse<SupabaseReviewRow[]>(\n request.order('created_at', { ascending: false }),\n 'supabase list review items'\n );\n\n return (rows ?? []).flatMap((row) => {\n const item = rowToReviewItem(row, options);\n return item ? [item] : [];\n });\n },\n\n async create(item) {\n const nextItem = normalizeItemForSupabaseCreate(item, source, options);\n\n if (options.unsafeClientReviewNumberFallback) {\n throw new Error(\n 'supabase create review item: unsafeClientReviewNumberFallback is no longer supported. Use create_review_item RPC with database-backed review_number sequence.'\n );\n }\n\n return createItemWithRpc(nextItem, source, options);\n },\n\n async update(id, patch) {\n const current = await this.get(id);\n if (!current) throw new Error(`Review item not found: ${id}`);\n\n const nextStatus = patch.status\n ? normalizeReviewItemStatus(patch.status)\n : current.status;\n const nextItem: ReviewItem = {\n ...current,\n ...patch,\n id,\n status: nextStatus,\n createdAt: current.createdAt,\n updatedAt: new Date().toISOString(),\n };\n const patchRow = itemToRowPatch(nextItem, source, options);\n const updated = await unwrapResponse<SupabaseReviewRow>(\n fromTable().update(patchRow).eq('id', id).select('*').single(),\n 'supabase update review item'\n );\n\n return rowToReviewItem(updated, options) ?? nextItem;\n },\n\n async remove(id) {\n await unwrapResponse<null>(\n fromTable().delete().eq('id', id),\n 'supabase delete review item'\n );\n },\n };\n}\n\nfunction normalizeItemForSupabaseCreate(\n item: ReviewItem,\n source: ReviewSource,\n options: SupabaseReviewAdapterOptions\n): ReviewItem {\n const now = new Date().toISOString();\n const id = createSupabaseReviewItemId();\n const normalizedStatus = normalizeReviewItemStatus(item.status);\n const routeKey = item.routeKey || item.normalizedPath || '/';\n const viewport = item.viewport ?? { width: 390, height: 720 };\n const nextItem: ReviewItem = {\n ...item,\n id,\n reviewNumber: undefined,\n projectId: options.projectId,\n routeKey,\n normalizedPath: item.normalizedPath || routeKey,\n viewport,\n status: normalizedStatus,\n externalIssueId: id,\n externalIssueUrl: buildSupabaseReviewUrl(\n { routeKey, normalizedPath: item.normalizedPath || routeKey, viewport },\n source,\n options,\n id\n ),\n submittedAt: item.submittedAt ?? now,\n submitStatus: item.submitStatus ?? 'submitted',\n createdAt: now,\n updatedAt: now,\n };\n\n return {\n ...nextItem,\n externalIssueUrl:\n nextItem.externalIssueUrl ??\n buildSupabaseReviewUrl(nextItem, source, options),\n };\n}\n\nasync function createItemWithRpc(\n item: ReviewItem,\n source: ReviewSource,\n options: SupabaseReviewAdapterOptions\n) {\n const rpcName =\n options.createRpc ?? DEFAULT_SUPABASE_CREATE_REVIEW_ITEM_RPC;\n\n if (!options.client.rpc) {\n throw new Error(\n `supabase create review item: ${rpcName} rpc is required`\n );\n }\n\n const row = await unwrapResponse<SupabaseReviewRow>(\n options.client.rpc(rpcName, {\n p_id: item.id,\n p_project_id: options.projectId,\n p_route_key: item.routeKey || item.normalizedPath || '/',\n p_source: source,\n p_status: normalizeReviewItemStatus(item.status),\n p_item: item,\n }),\n `supabase create review item rpc ${rpcName}`\n );\n\n return rowToReviewItem(row, options) ?? item;\n}\n\nfunction itemToRow(\n item: ReviewItem,\n source: ReviewSource,\n options: SupabaseReviewAdapterOptions\n): SupabaseReviewRow {\n const now = new Date().toISOString();\n const updatedAt = item.updatedAt || now;\n\n return {\n id: item.id,\n project_id: options.projectId,\n route_key: item.routeKey || item.normalizedPath || '/',\n source,\n review_number: item.reviewNumber ?? null,\n status: normalizeReviewItemStatus(item.status),\n item: {\n ...item,\n projectId: options.projectId,\n status: normalizeReviewItemStatus(item.status),\n updatedAt,\n },\n created_at: item.createdAt || now,\n updated_at: updatedAt,\n };\n}\n\nfunction itemToRowPatch(\n item: ReviewItem,\n source: ReviewSource,\n options: SupabaseReviewAdapterOptions\n) {\n const row = itemToRow(item, source, options);\n return {\n route_key: row.route_key,\n review_number: row.review_number,\n status: row.status,\n item: row.item,\n updated_at: row.updated_at,\n };\n}\n\nfunction rowToReviewItem(\n row: SupabaseReviewRow,\n options: SupabaseReviewAdapterOptions\n): ReviewItem | null {\n if (!row.item || typeof row.item !== 'object') return null;\n\n const item = row.item as Partial<ReviewItem>;\n const status = normalizeReviewItemStatus(\n (row.status || item.status || 'todo') as ReviewItemStatus\n );\n const routeKey = row.route_key || item.routeKey || item.normalizedPath || '/';\n const viewport = item.viewport ?? { width: 390, height: 720 };\n const now = new Date().toISOString();\n\n return {\n ...(item as ReviewItem),\n id: row.id,\n reviewNumber: row.review_number ?? item.reviewNumber,\n projectId: row.project_id || item.projectId || options.projectId,\n routeKey,\n pageUrl: item.pageUrl || toAbsoluteReviewUrl(routeKey),\n normalizedPath: item.normalizedPath || routeKey,\n kind: item.kind === 'area' ? 'area' : 'note',\n comment: item.comment || '',\n status,\n viewport,\n externalIssueId: item.externalIssueId ?? row.id,\n externalIssueUrl:\n item.externalIssueUrl ??\n buildSupabaseReviewUrl(\n { routeKey, normalizedPath: routeKey, viewport },\n row.source,\n options,\n row.id\n ),\n submittedAt: item.submittedAt ?? row.created_at,\n submitStatus: item.submitStatus ?? 'submitted',\n createdAt: item.createdAt ?? row.created_at ?? now,\n updatedAt: row.updated_at ?? item.updatedAt ?? now,\n };\n}\n\nasync function unwrapResponse<T>(\n request: Promise<SupabaseResponse<T>> | SupabaseResponse<T>,\n label: string\n) {\n const { data, error } = await request;\n if (error) {\n throw new Error(`${label}: ${error.message ?? error.code ?? 'failed'}`);\n }\n return data as T;\n}\n\nfunction buildSupabaseReviewUrl(\n item: Pick<ReviewItem, 'routeKey' | 'normalizedPath' | 'viewport'>,\n source: ReviewSource,\n options: SupabaseReviewAdapterOptions,\n itemId?: string\n) {\n if (typeof window === 'undefined') return undefined;\n\n const url = new URL(\n normalizeReviewUrlPath(options.reviewPathPrefix),\n window.location.origin\n );\n url.searchParams.set('source', source);\n url.searchParams.set('target', item.routeKey || item.normalizedPath || '/');\n url.searchParams.set('w', String(Math.round(item.viewport.width)));\n url.searchParams.set('h', String(Math.round(item.viewport.height)));\n if (itemId) url.searchParams.set('item', itemId);\n return url.toString();\n}\n\nfunction normalizeReviewUrlPath(value = '/review') {\n const raw = value.trim() || '/review';\n const path = raw.startsWith('/') ? raw : `/${raw}`;\n return path.endsWith('/') ? path : `${path}/`;\n}\n\nfunction toAbsoluteReviewUrl(path: string) {\n if (typeof window === 'undefined') return path;\n return new URL(path, window.location.origin).toString();\n}\n\nfunction createSupabaseReviewItemId() {\n if (typeof crypto !== 'undefined' && crypto.randomUUID) {\n return crypto.randomUUID();\n }\n\n return `review-${Date.now()}-${Math.random().toString(36).slice(2, 10)}`;\n}\n"],"mappings":"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","names":[]}
1
+ {"version":3,"sources":["../src/adapters/supabase.ts","../src/figma/remote.image.store.ts"],"sourcesContent":["import { normalizeReviewItemStatus } from '../status';\nimport type {\n ReviewItem,\n ReviewItemStatus,\n ReviewSource,\n SupabaseReviewAdapterOptions,\n WebReviewKitAdapter,\n} from '../types';\n\nconst DEFAULT_SUPABASE_REVIEW_TABLE = 'review_items';\nconst DEFAULT_SUPABASE_REVIEW_SOURCE = 'supabase';\nconst DEFAULT_SUPABASE_CREATE_REVIEW_ITEM_RPC = 'create_review_item';\n\ntype SupabaseReviewRow = {\n id: string;\n project_id: string;\n route_key: string;\n source: string;\n review_number?: number | null;\n status: string;\n item: unknown;\n created_at: string;\n updated_at: string;\n};\n\ntype SupabaseResponse<T> = {\n data: T | null;\n error: { message?: string; code?: string } | null;\n};\n\nexport function supabaseAdapter(\n options: SupabaseReviewAdapterOptions\n): WebReviewKitAdapter {\n const tableName = options.table ?? DEFAULT_SUPABASE_REVIEW_TABLE;\n const source = options.source ?? DEFAULT_SUPABASE_REVIEW_SOURCE;\n\n const fromTable = () => options.client.from(tableName);\n\n return {\n async get(id) {\n const row = await unwrapResponse<SupabaseReviewRow | null>(\n fromTable().select('*').eq('id', id).maybeSingle(),\n 'supabase get review item'\n );\n\n return row ? rowToReviewItem(row, options) : null;\n },\n\n async list(query) {\n let request = fromTable()\n .select('*')\n .eq('project_id', query.projectId)\n .eq('source', query.source ?? source);\n const routeKey = query.routeKey ?? query.normalizedPath;\n\n if (routeKey) {\n request = request.eq('route_key', routeKey);\n }\n if (query.status) {\n request = request.eq(\n 'status',\n normalizeReviewItemStatus(query.status)\n );\n }\n\n const rows = await unwrapResponse<SupabaseReviewRow[]>(\n request.order('created_at', { ascending: false }),\n 'supabase list review items'\n );\n\n return (rows ?? []).flatMap((row) => {\n const item = rowToReviewItem(row, options);\n return item ? [item] : [];\n });\n },\n\n async create(item) {\n const nextItem = normalizeItemForSupabaseCreate(item, source, options);\n\n if (options.unsafeClientReviewNumberFallback) {\n throw new Error(\n 'supabase create review item: unsafeClientReviewNumberFallback is no longer supported. Use create_review_item RPC with database-backed review_number sequence.'\n );\n }\n\n return createItemWithRpc(nextItem, source, options);\n },\n\n async update(id, patch) {\n const current = await this.get(id);\n if (!current) throw new Error(`Review item not found: ${id}`);\n\n const nextStatus = patch.status\n ? normalizeReviewItemStatus(patch.status)\n : current.status;\n const nextItem: ReviewItem = {\n ...current,\n ...patch,\n id,\n status: nextStatus,\n createdAt: current.createdAt,\n updatedAt: new Date().toISOString(),\n };\n const patchRow = itemToRowPatch(nextItem, source, options);\n const updated = await unwrapResponse<SupabaseReviewRow>(\n fromTable().update(patchRow).eq('id', id).select('*').single(),\n 'supabase update review item'\n );\n\n return rowToReviewItem(updated, options) ?? nextItem;\n },\n\n async remove(id) {\n await unwrapResponse<null>(\n fromTable().delete().eq('id', id),\n 'supabase delete review item'\n );\n },\n };\n}\n\nfunction normalizeItemForSupabaseCreate(\n item: ReviewItem,\n source: ReviewSource,\n options: SupabaseReviewAdapterOptions\n): ReviewItem {\n const now = new Date().toISOString();\n const id = createSupabaseReviewItemId();\n const normalizedStatus = normalizeReviewItemStatus(item.status);\n const routeKey = item.routeKey || item.normalizedPath || '/';\n const viewport = item.viewport ?? { width: 390, height: 720 };\n const nextItem: ReviewItem = {\n ...item,\n id,\n reviewNumber: undefined,\n projectId: options.projectId,\n routeKey,\n normalizedPath: item.normalizedPath || routeKey,\n viewport,\n status: normalizedStatus,\n externalIssueId: id,\n externalIssueUrl: buildSupabaseReviewUrl(\n { routeKey, normalizedPath: item.normalizedPath || routeKey, viewport },\n source,\n options,\n id\n ),\n submittedAt: item.submittedAt ?? now,\n submitStatus: item.submitStatus ?? 'submitted',\n createdAt: now,\n updatedAt: now,\n };\n\n return {\n ...nextItem,\n externalIssueUrl:\n nextItem.externalIssueUrl ??\n buildSupabaseReviewUrl(nextItem, source, options),\n };\n}\n\nasync function createItemWithRpc(\n item: ReviewItem,\n source: ReviewSource,\n options: SupabaseReviewAdapterOptions\n) {\n const rpcName =\n options.createRpc ?? DEFAULT_SUPABASE_CREATE_REVIEW_ITEM_RPC;\n\n if (!options.client.rpc) {\n throw new Error(\n `supabase create review item: ${rpcName} rpc is required`\n );\n }\n\n const row = await unwrapResponse<SupabaseReviewRow>(\n options.client.rpc(rpcName, {\n p_id: item.id,\n p_project_id: options.projectId,\n p_route_key: item.routeKey || item.normalizedPath || '/',\n p_source: source,\n p_status: normalizeReviewItemStatus(item.status),\n p_item: item,\n }),\n `supabase create review item rpc ${rpcName}`\n );\n\n return rowToReviewItem(row, options) ?? item;\n}\n\nfunction itemToRow(\n item: ReviewItem,\n source: ReviewSource,\n options: SupabaseReviewAdapterOptions\n): SupabaseReviewRow {\n const now = new Date().toISOString();\n const updatedAt = item.updatedAt || now;\n\n return {\n id: item.id,\n project_id: options.projectId,\n route_key: item.routeKey || item.normalizedPath || '/',\n source,\n review_number: item.reviewNumber ?? null,\n status: normalizeReviewItemStatus(item.status),\n item: {\n ...item,\n projectId: options.projectId,\n status: normalizeReviewItemStatus(item.status),\n updatedAt,\n },\n created_at: item.createdAt || now,\n updated_at: updatedAt,\n };\n}\n\nfunction itemToRowPatch(\n item: ReviewItem,\n source: ReviewSource,\n options: SupabaseReviewAdapterOptions\n) {\n const row = itemToRow(item, source, options);\n return {\n route_key: row.route_key,\n review_number: row.review_number,\n status: row.status,\n item: row.item,\n updated_at: row.updated_at,\n };\n}\n\nfunction rowToReviewItem(\n row: SupabaseReviewRow,\n options: SupabaseReviewAdapterOptions\n): ReviewItem | null {\n if (!row.item || typeof row.item !== 'object') return null;\n\n const item = row.item as Partial<ReviewItem>;\n const status = normalizeReviewItemStatus(\n (row.status || item.status || 'todo') as ReviewItemStatus\n );\n const routeKey = row.route_key || item.routeKey || item.normalizedPath || '/';\n const viewport = item.viewport ?? { width: 390, height: 720 };\n const now = new Date().toISOString();\n\n return {\n ...(item as ReviewItem),\n id: row.id,\n reviewNumber: row.review_number ?? item.reviewNumber,\n projectId: row.project_id || item.projectId || options.projectId,\n routeKey,\n pageUrl: item.pageUrl || toAbsoluteReviewUrl(routeKey),\n normalizedPath: item.normalizedPath || routeKey,\n kind: item.kind === 'area' ? 'area' : 'note',\n comment: item.comment || '',\n status,\n viewport,\n externalIssueId: item.externalIssueId ?? row.id,\n externalIssueUrl:\n item.externalIssueUrl ??\n buildSupabaseReviewUrl(\n { routeKey, normalizedPath: routeKey, viewport },\n row.source,\n options,\n row.id\n ),\n submittedAt: item.submittedAt ?? row.created_at,\n submitStatus: item.submitStatus ?? 'submitted',\n createdAt: item.createdAt ?? row.created_at ?? now,\n updatedAt: row.updated_at ?? item.updatedAt ?? now,\n };\n}\n\nasync function unwrapResponse<T>(\n request: Promise<SupabaseResponse<T>> | SupabaseResponse<T>,\n label: string\n) {\n const { data, error } = await request;\n if (error) {\n throw new Error(`${label}: ${error.message ?? error.code ?? 'failed'}`);\n }\n return data as T;\n}\n\nfunction buildSupabaseReviewUrl(\n item: Pick<ReviewItem, 'routeKey' | 'normalizedPath' | 'viewport'>,\n source: ReviewSource,\n options: SupabaseReviewAdapterOptions,\n itemId?: string\n) {\n if (typeof window === 'undefined') return undefined;\n\n const url = new URL(\n normalizeReviewUrlPath(options.reviewPathPrefix),\n window.location.origin\n );\n url.searchParams.set('source', source);\n url.searchParams.set('target', item.routeKey || item.normalizedPath || '/');\n url.searchParams.set('w', String(Math.round(item.viewport.width)));\n url.searchParams.set('h', String(Math.round(item.viewport.height)));\n if (itemId) url.searchParams.set('item', itemId);\n return url.toString();\n}\n\nfunction normalizeReviewUrlPath(value = '/review') {\n const raw = value.trim() || '/review';\n const path = raw.startsWith('/') ? raw : `/${raw}`;\n return path.endsWith('/') ? path : `${path}/`;\n}\n\nfunction toAbsoluteReviewUrl(path: string) {\n if (typeof window === 'undefined') return path;\n return new URL(path, window.location.origin).toString();\n}\n\nfunction createSupabaseReviewItemId() {\n if (typeof crypto !== 'undefined' && crypto.randomUUID) {\n return crypto.randomUUID();\n }\n\n return `review-${Date.now()}-${Math.random().toString(36).slice(2, 10)}`;\n}\n","import type {\n AddReviewFigmaImageInput,\n ReviewFigmaImage,\n ReviewFigmaImageAssetInput,\n ReviewFigmaImageFormat,\n ReviewFigmaImageStore,\n ReviewFigmaImageTarget,\n UpdateReviewFigmaImageInput,\n} from './image.types';\nimport {\n createReviewFigmaClientRenderedAsset,\n getReviewFigmaImageTargetKey,\n type ReviewFigmaImageClientRenderOptions,\n} from './image.store';\nimport { parseReviewFigmaNodeRef } from './parse';\n\nexport const DEFAULT_REVIEW_FIGMA_REMOTE_IMAGES_TABLE = 'review_figma_images';\nconst DEFAULT_REVIEW_FIGMA_REMOTE_UPLOAD_TIMEOUT_MS = 30000;\nconst FIGMA_TOKEN_STORAGE_KEY = 'figma-token';\n\ntype ReviewFigmaRemoteTokenProvider =\n | string\n | null\n | undefined\n | (() => string | null | undefined);\n\nexport type ReviewFigmaRemoteImageRow = {\n id: string;\n project_id: string;\n target_key: string;\n target: ReviewFigmaImageTarget;\n target_type: 'route' | 'figma-node';\n page_url: string | null;\n viewport_label: string | null;\n viewport_width: number | null;\n viewport_height: number | null;\n viewport_scope: 'mobile' | 'tablet' | 'desktop' | 'wide' | null;\n slot: string | null;\n figma_url: string;\n file_key: string;\n node_id: string;\n image_url: string;\n image_format: ReviewFigmaImageFormat;\n mime_type: string;\n storage_key: string;\n label: string | null;\n sort_order: number;\n width: number | null;\n height: number | null;\n byte_size: number | null;\n created_at: string;\n updated_at: string;\n};\n\ntype ReviewFigmaRemoteDbResult<T = unknown> = {\n data: T | null;\n error: unknown;\n};\n\ntype ReviewFigmaRemoteDbQuery<T = unknown> = PromiseLike<\n ReviewFigmaRemoteDbResult<T>\n> & {\n delete(): ReviewFigmaRemoteDbQuery<T>;\n eq(column: string, value: unknown): ReviewFigmaRemoteDbQuery<T>;\n insert(value: unknown): ReviewFigmaRemoteDbQuery<T>;\n order(\n column: string,\n options?: { ascending?: boolean }\n ): ReviewFigmaRemoteDbQuery<T>;\n select(columns?: string): ReviewFigmaRemoteDbQuery<T>;\n single(): PromiseLike<ReviewFigmaRemoteDbResult<T>>;\n update(value: unknown): ReviewFigmaRemoteDbQuery<T>;\n};\n\nexport type ReviewFigmaRemoteDbClient = {\n from(table: string): ReviewFigmaRemoteDbQuery;\n};\n\nexport type ReviewFigmaRemoteAssetUploadResponse = {\n storageKey?: unknown;\n r2Key?: unknown;\n imageUrl?: unknown;\n publicUrl?: unknown;\n error?: unknown;\n};\n\nexport type RemoteReviewFigmaImageStoreOptions = {\n client: ReviewFigmaRemoteDbClient;\n uploadEndpoint: string;\n table?: string;\n fetch?: typeof fetch;\n token?: ReviewFigmaRemoteTokenProvider;\n clientRender?: boolean | ReviewFigmaImageClientRenderOptions;\n uploadTimeoutMs?: number;\n};\n\nexport function createRemoteReviewFigmaImageStore({\n client,\n uploadEndpoint,\n table = DEFAULT_REVIEW_FIGMA_REMOTE_IMAGES_TABLE,\n fetch: fetchOption,\n token,\n clientRender = true,\n uploadTimeoutMs = DEFAULT_REVIEW_FIGMA_REMOTE_UPLOAD_TIMEOUT_MS,\n}: RemoteReviewFigmaImageStoreOptions): ReviewFigmaImageStore {\n const listImages = async (target: ReviewFigmaImageTarget) => {\n const targetKey = getReviewFigmaImageTargetKey(target);\n const { data, error } = await client\n .from(table)\n .select('*')\n .eq('project_id', target.projectId)\n .eq('target_key', targetKey)\n .order('sort_order', { ascending: true })\n .order('created_at', { ascending: true });\n\n if (error) throwRemoteStoreError(error, 'list Figma images');\n return ((data ?? []) as ReviewFigmaRemoteImageRow[]).map(rowToFigmaImage);\n };\n\n return {\n listImages,\n async addImage(input) {\n const ref = parseReviewFigmaNodeRef(input.figmaUrl);\n if (!ref) {\n throw new Error('A Figma node link or fileKey->nodeId value is required.');\n }\n\n const id = createFigmaImageId();\n const asset = await resolveFigmaImageAsset({\n input,\n token,\n clientRender,\n fetch: fetchOption,\n });\n const uploaded = await uploadFigmaAsset({\n asset,\n endpoint: uploadEndpoint,\n fetch: fetchOption,\n imageId: id,\n projectId: input.target.projectId,\n timeoutMs: uploadTimeoutMs,\n });\n const order =\n typeof input.order === 'number' && Number.isFinite(input.order)\n ? input.order\n : await getNextImageOrder(listImages, input.target);\n const now = new Date().toISOString();\n const row = createImageRow({\n asset,\n fileKey: ref.fileKey,\n id,\n imageUrl: uploaded.imageUrl,\n input,\n nodeId: ref.nodeId,\n now,\n order,\n storageKey: uploaded.storageKey,\n });\n const { data, error } = await client\n .from(table)\n .insert(row)\n .select('*')\n .single();\n\n if (error) throwRemoteStoreError(error, 'insert Figma image');\n return rowToFigmaImage(data as ReviewFigmaRemoteImageRow);\n },\n async updateImage(id, patch) {\n const { data, error } = await client\n .from(table)\n .update(createUpdatePatch(patch))\n .eq('id', id)\n .select('*')\n .single();\n\n if (error) throwRemoteStoreError(error, 'update Figma image');\n return rowToFigmaImage(data as ReviewFigmaRemoteImageRow);\n },\n async reorderImages(input) {\n const current = await listImages(input.target);\n const currentIds = new Set(current.map((image) => image.id));\n const nextIds = [\n ...input.imageIds.filter((id) => currentIds.has(id)),\n ...current\n .map((image) => image.id)\n .filter((id) => !input.imageIds.includes(id)),\n ];\n const targetKey = getReviewFigmaImageTargetKey(input.target);\n const updatedAt = new Date().toISOString();\n\n const results = await Promise.all(\n nextIds.map((id, index) =>\n client\n .from(table)\n .update({ sort_order: index, updated_at: updatedAt })\n .eq('id', id)\n .eq('project_id', input.target.projectId)\n .eq('target_key', targetKey)\n )\n );\n const failed = results.find((result) => result.error);\n if (failed?.error) throwRemoteStoreError(failed.error, 'reorder Figma images');\n\n return listImages(input.target);\n },\n async deleteImage(id) {\n const { error } = await client.from(table).delete().eq('id', id);\n if (error) throwRemoteStoreError(error, 'delete Figma image');\n },\n };\n}\n\nasync function resolveFigmaImageAsset({\n input,\n token,\n clientRender,\n fetch,\n}: {\n input: AddReviewFigmaImageInput;\n token: ReviewFigmaRemoteTokenProvider;\n clientRender: boolean | ReviewFigmaImageClientRenderOptions;\n fetch: typeof globalThis.fetch | undefined;\n}) {\n if (input.asset) return input.asset;\n if (!clientRender) {\n throw new Error('Figma image asset is required when clientRender is disabled.');\n }\n\n const renderOptions = clientRender === true ? {} : clientRender;\n const figmaToken = readRemoteFigmaToken(renderOptions.token ?? token);\n if (!figmaToken) throw new Error('Figma token is required.');\n\n return createReviewFigmaClientRenderedAsset({\n ...renderOptions,\n fetch,\n figmaUrl: input.figmaUrl,\n token: figmaToken,\n });\n}\n\nasync function uploadFigmaAsset({\n asset,\n endpoint,\n fetch: fetchOption,\n imageId,\n projectId,\n timeoutMs,\n}: {\n asset: ReviewFigmaImageAssetInput;\n endpoint: string;\n fetch: typeof globalThis.fetch | undefined;\n imageId: string;\n projectId: string;\n timeoutMs: number;\n}) {\n const requestFetch = fetchOption ?? globalThis.fetch;\n if (!requestFetch) throw new Error('Figma asset upload requires fetch.');\n\n const { blob, mimeType } = decodeAssetDataUrl(asset);\n const response = await withTimeout(\n requestFetch(\n createAssetUploadUrl(endpoint, {\n imageFormat: asset.imageFormat,\n imageId,\n projectId,\n }),\n {\n method: 'POST',\n headers: {\n 'Content-Type': mimeType,\n },\n body: blob,\n }\n ),\n timeoutMs,\n 'Figma asset upload timed out.'\n );\n const data = (await response.json().catch(() => null)) as\n | ReviewFigmaRemoteAssetUploadResponse\n | null;\n\n if (!response.ok) {\n throw new Error(\n readString(data?.error) || `Figma asset upload failed with ${response.status}`\n );\n }\n\n const storageKey = readString(data?.storageKey) ?? readString(data?.r2Key);\n const imageUrl = readString(data?.imageUrl) ?? readString(data?.publicUrl);\n if (!storageKey || !imageUrl) {\n throw new Error('Figma asset upload response is missing image URL.');\n }\n\n return { storageKey, imageUrl };\n}\n\nfunction createAssetUploadUrl(\n endpoint: string,\n params: {\n imageFormat: ReviewFigmaImageFormat;\n imageId: string;\n projectId: string;\n }\n) {\n const base =\n typeof window !== 'undefined' ? window.location.origin : 'http://localhost';\n const url = new URL(endpoint, base);\n url.searchParams.set('projectId', params.projectId);\n url.searchParams.set('imageId', params.imageId);\n url.searchParams.set('imageFormat', params.imageFormat);\n return url.toString();\n}\n\nfunction createImageRow({\n asset,\n fileKey,\n id,\n imageUrl,\n input,\n nodeId,\n now,\n order,\n storageKey,\n}: {\n asset: ReviewFigmaImageAssetInput;\n fileKey: string;\n id: string;\n imageUrl: string;\n input: AddReviewFigmaImageInput;\n nodeId: string;\n now: string;\n order: number;\n storageKey: string;\n}): ReviewFigmaRemoteImageRow {\n const target = input.target;\n const targetKey = getReviewFigmaImageTargetKey(target);\n\n return {\n id,\n project_id: target.projectId,\n target_key: targetKey,\n target,\n target_type: target.type,\n page_url: target.type === 'route' ? target.pageUrl : null,\n viewport_label: target.type === 'route' ? target.viewport?.label ?? null : null,\n viewport_width: target.type === 'route' ? target.viewport?.width ?? null : null,\n viewport_height: target.type === 'route' ? target.viewport?.height ?? null : null,\n viewport_scope: target.type === 'route' ? target.viewport?.scope ?? null : null,\n slot: target.type === 'route' ? target.slot ?? null : null,\n figma_url: input.figmaUrl,\n file_key: fileKey,\n node_id: nodeId,\n image_url: imageUrl,\n image_format: asset.imageFormat,\n mime_type: asset.mimeType,\n storage_key: storageKey,\n label: normalizeOptionalText(input.label) ?? null,\n sort_order: order,\n width: asset.width ?? null,\n height: asset.height ?? null,\n byte_size: asset.byteSize ?? null,\n created_at: now,\n updated_at: now,\n };\n}\n\nfunction createUpdatePatch(patch: UpdateReviewFigmaImageInput) {\n return {\n ...(patch.label !== undefined\n ? { label: normalizeOptionalText(patch.label) ?? null }\n : {}),\n ...(typeof patch.order === 'number' && Number.isFinite(patch.order)\n ? { sort_order: patch.order }\n : {}),\n updated_at: new Date().toISOString(),\n };\n}\n\nasync function getNextImageOrder(\n listImages: (target: ReviewFigmaImageTarget) => Promise<ReviewFigmaImage[]>,\n target: ReviewFigmaImageTarget\n) {\n const images = await listImages(target);\n return images.length\n ? Math.max(...images.map((image) => image.order)) + 1\n : 0;\n}\n\nfunction rowToFigmaImage(row: ReviewFigmaRemoteImageRow): ReviewFigmaImage {\n return {\n id: row.id,\n projectId: row.project_id,\n target: row.target,\n figmaUrl: row.figma_url,\n fileKey: row.file_key,\n nodeId: row.node_id,\n imageUrl: row.image_url,\n imageFormat: row.image_format,\n mimeType: row.mime_type,\n label: row.label ?? undefined,\n order: row.sort_order,\n storageKey: row.storage_key,\n width: row.width ?? undefined,\n height: row.height ?? undefined,\n byteSize: row.byte_size ?? undefined,\n createdAt: row.created_at,\n updatedAt: row.updated_at,\n };\n}\n\nfunction decodeAssetDataUrl(asset: ReviewFigmaImageAssetInput) {\n const match = asset.dataUrl.match(/^data:([^;,]+);base64,(.*)$/);\n if (!match) throw new Error('Valid Figma image asset data URL is required.');\n\n const mimeType = match[1]?.trim();\n if (!mimeType || mimeType !== asset.mimeType) {\n throw new Error('Figma image asset MIME type mismatch.');\n }\n\n const binary = globalThis.atob(match[2] ?? '');\n const bytes = new Uint8Array(binary.length);\n for (let index = 0; index < binary.length; index += 1) {\n bytes[index] = binary.charCodeAt(index);\n }\n\n return {\n blob: new Blob([bytes], { type: mimeType }),\n mimeType,\n };\n}\n\nasync function withTimeout<T>(\n promise: Promise<T>,\n timeoutMs: number,\n message: string\n) {\n let timeoutId: ReturnType<typeof setTimeout> | undefined;\n try {\n return await Promise.race([\n promise,\n new Promise<T>((_, reject) => {\n timeoutId = setTimeout(() => reject(new Error(message)), timeoutMs);\n }),\n ]);\n } finally {\n if (timeoutId) clearTimeout(timeoutId);\n }\n}\n\nfunction readRemoteFigmaToken(provider: ReviewFigmaRemoteTokenProvider) {\n const token = typeof provider === 'function' ? provider() : provider;\n if (typeof token === 'string' && token.trim()) return token.trim();\n return readStoredFigmaToken();\n}\n\nfunction readStoredFigmaToken() {\n if (typeof window === 'undefined') return '';\n\n try {\n return window.localStorage.getItem(FIGMA_TOKEN_STORAGE_KEY)?.trim() || '';\n } catch {\n return '';\n }\n}\n\nfunction createFigmaImageId() {\n const id =\n globalThis.crypto?.randomUUID?.() ?? Math.random().toString(36).slice(2);\n return `figma_${id.replace(/[^A-Za-z0-9_-]+/g, '-')}`;\n}\n\nfunction normalizeOptionalText(value: string | null | undefined) {\n if (typeof value !== 'string') return undefined;\n return value.trim() || undefined;\n}\n\nfunction readString(value: unknown) {\n return typeof value === 'string' && value.trim() ? value.trim() : null;\n}\n\nfunction throwRemoteStoreError(error: unknown, action: string): never {\n const source = error as {\n code?: unknown;\n details?: unknown;\n hint?: unknown;\n message?: unknown;\n };\n const message = readString(source.message) ?? `Remote Figma image ${action} failed.`;\n const code = readString(source.code);\n const details = readString(source.details);\n const hint = readString(source.hint);\n const suffix = [code, details, hint].filter(Boolean).join(' / ');\n\n throw new Error(suffix ? `${message} (${suffix})` : message);\n}\n"],"mappings":"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","names":[]}