@designfever/web-review-kit 0.7.1 → 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/README.md +1 -1
- package/dist/{chunk-2ZLU5FTD.js → chunk-K26WLRRP.js} +19 -5
- package/dist/chunk-K26WLRRP.js.map +1 -0
- package/dist/index.cjs +315 -4
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +66 -1
- package/dist/index.d.ts +66 -1
- package/dist/index.js +298 -1
- package/dist/index.js.map +1 -1
- package/dist/vite.cjs.map +1 -1
- package/dist/vite.js +1 -1
- package/docs/README.md +5 -3
- package/docs/adapters.md +29 -1
- package/docs/release-notes-0.7.2.md +33 -0
- package/package.json +1 -1
- package/dist/chunk-2ZLU5FTD.js.map +0 -1
package/dist/index.d.cts
CHANGED
|
@@ -37,6 +37,71 @@ declare function createReviewFigmaImageStoreClient(options?: ReviewFigmaImageSto
|
|
|
37
37
|
declare function getReviewFigmaImageTargetKey(target: ReviewFigmaImageTarget): string;
|
|
38
38
|
declare function getReviewFigmaImageMimeType(format: ReviewFigmaImageFormat): "image/webp" | "image/png" | "image/jpeg";
|
|
39
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
|
+
|
|
40
105
|
/** Creates the vanilla runtime controller that mounts review overlays on a target page. */
|
|
41
106
|
declare function createWebReviewKit(options: WebReviewKitOptions): WebReviewKitController;
|
|
42
107
|
|
|
@@ -53,4 +118,4 @@ declare function getReviewItemScopeLabel(item: ReviewItem, presets?: ReviewViewp
|
|
|
53
118
|
/** Adds scope-aware display labels to review items without mutating them. */
|
|
54
119
|
declare function getNumberedReviewItems(items: ReviewItem[], presets?: ReviewViewportPreset[]): NumberedReviewItem[];
|
|
55
120
|
|
|
56
|
-
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
|
@@ -37,6 +37,71 @@ declare function createReviewFigmaImageStoreClient(options?: ReviewFigmaImageSto
|
|
|
37
37
|
declare function getReviewFigmaImageTargetKey(target: ReviewFigmaImageTarget): string;
|
|
38
38
|
declare function getReviewFigmaImageMimeType(format: ReviewFigmaImageFormat): "image/webp" | "image/png" | "image/jpeg";
|
|
39
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
|
+
|
|
40
105
|
/** Creates the vanilla runtime controller that mounts review overlays on a target page. */
|
|
41
106
|
declare function createWebReviewKit(options: WebReviewKitOptions): WebReviewKitController;
|
|
42
107
|
|
|
@@ -53,4 +118,4 @@ declare function getReviewItemScopeLabel(item: ReviewItem, presets?: ReviewViewp
|
|
|
53
118
|
/** Adds scope-aware display labels to review items without mutating them. */
|
|
54
119
|
declare function getNumberedReviewItems(items: ReviewItem[], presets?: ReviewViewportPreset[]): NumberedReviewItem[];
|
|
55
120
|
|
|
56
|
-
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-
|
|
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":[]}
|