@scaleflex/dam-core 0.4.0 → 0.4.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/api/folders.service.d.ts +1 -1
- package/dist/api/folders.service.d.ts.map +1 -1
- package/dist/index.cjs +1 -1
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +35 -34
- package/dist/index.js.map +1 -1
- package/dist/types/api.types.d.ts +3 -0
- package/dist/types/api.types.d.ts.map +1 -1
- package/package.json +1 -1
- package/dist/api/archive.test.d.ts +0 -2
- package/dist/api/archive.test.d.ts.map +0 -1
- package/dist/api/files.service.test.d.ts +0 -2
- package/dist/api/files.service.test.d.ts.map +0 -1
- package/dist/api/variations.service.test.d.ts +0 -2
- package/dist/api/variations.service.test.d.ts.map +0 -1
- package/dist/auth/auth.service.test.d.ts +0 -2
- package/dist/auth/auth.service.test.d.ts.map +0 -1
- package/dist/helpers/cdn.test.d.ts +0 -2
- package/dist/helpers/cdn.test.d.ts.map +0 -1
- package/dist/helpers/format.test.d.ts +0 -2
- package/dist/helpers/format.test.d.ts.map +0 -1
- package/dist/helpers/thumbnail.test.d.ts +0 -2
- package/dist/helpers/thumbnail.test.d.ts.map +0 -1
- package/dist/helpers/variations.test.d.ts +0 -2
- package/dist/helpers/variations.test.d.ts.map +0 -1
- package/dist/store/browse-store.test.d.ts +0 -2
- package/dist/store/browse-store.test.d.ts.map +0 -1
package/dist/index.cjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.cjs","sources":["../src/auth/auth.service.ts","../src/api/dam-client.ts","../src/api/folders.service.ts","../src/api/files.service.ts","../src/api/labels.service.ts","../src/helpers/variations.ts","../src/api/variations.service.ts","../src/api/archive.service.ts","../src/api/filerobot-browse.service.ts","../src/store/browse-store.ts","../src/store/local-favourites.ts","../src/helpers/cdn.ts","../src/helpers/thumbnail.ts","../src/helpers/format.ts","../src/helpers/debounce.ts"],"sourcesContent":["// @scaleflex/dam-core — canonical auth service.\n//\n// The single source of truth for Filerobot auth across the widgets. Accepts the canonical\n// (`container`) shape AND asset-picker's drifted (`projectToken`) shape, normalizing the\n// latter. See plan/05 + plan/11 (F1).\nimport type {\n AnyAuthConfig,\n AssetPickerAuth,\n AuthConfig,\n AuthHeaders,\n ResolvedAuth,\n} from './auth.types';\n\ninterface SassKeyResponse {\n status: 'success' | 'error';\n key: string;\n msg?: string;\n}\n\nconst DEFAULT_API_HOST = 'https://api.filerobot.com';\n\nconst EXCHANGE_TIMEOUT_MS = 30_000;\nconst DEFAULT_EXCHANGE_RETRIES = 2;\nconst DEFAULT_EXCHANGE_RETRY_DELAY_MS = 400;\n\n/**\n * Whether an exchange HTTP status is worth retrying. The Filerobot key endpoint\n * can return a transient **404** (as well as 408/429/5xx) for a perfectly valid\n * security template — e.g. an edge/cache miss — so a valid config intermittently\n * fails the exchange. Retrying these a few times absorbs the blip.\n */\nfunction isTransientStatus(status: number): boolean {\n return status === 404 || status === 408 || status === 429 || status >= 500;\n}\n\nconst sleep = (ms: number) => new Promise<void>((resolve) => setTimeout(resolve, ms));\n\n/**\n * Container base URL, e.g. \"https://api.filerobot.com/akli\".\n * `apiDomain` overrides the default host. The REST client appends `/v5` to this\n * for the Files/Folders API.\n */\nexport function getApiBase(container: string, apiDomain?: string): string {\n const host = (apiDomain || DEFAULT_API_HOST).replace(/\\/+$/, '');\n return `${host}/${container}`;\n}\n\n/**\n * Exchange a security template id for a short-lived SASS key.\n *\n * Resilience: the response is fetched with `cache: 'no-store'` (the key is\n * short-lived and security-sensitive — never serve a cached key or a cached\n * transient 404), and transient failures (404/408/429/5xx, network errors) are\n * retried a few times with linear backoff. This fixes the intermittent\n * \"SASS key exchange failed (HTTP 404)\" seen with a valid container + template.\n */\nexport async function exchangeSassKey(\n container: string,\n securityTemplateId: string,\n apiDomain?: string,\n opts: { retries?: number; retryDelayMs?: number } = {},\n): Promise<string> {\n const url = `${getApiBase(container, apiDomain)}/key/${encodeURIComponent(securityTemplateId)}`;\n const retries = opts.retries ?? DEFAULT_EXCHANGE_RETRIES;\n const retryDelayMs = opts.retryDelayMs ?? DEFAULT_EXCHANGE_RETRY_DELAY_MS;\n\n let lastError: Error = new Error('SASS key exchange failed');\n\n for (let attempt = 0; attempt <= retries; attempt++) {\n if (attempt > 0) await sleep(retryDelayMs * attempt);\n\n const controller = new AbortController();\n const timeoutId = setTimeout(() => controller.abort(), EXCHANGE_TIMEOUT_MS);\n\n try {\n const response = await fetch(url, { signal: controller.signal, cache: 'no-store' });\n clearTimeout(timeoutId);\n\n if (!response.ok) {\n lastError = new Error(`SASS key exchange failed (HTTP ${response.status})`);\n if (isTransientStatus(response.status) && attempt < retries) continue;\n throw lastError;\n }\n\n const data: SassKeyResponse = await response.json();\n if (data.status === 'error') {\n // A definitive backend rejection (bad/expired template) — don't retry.\n throw new Error(`SASS key exchange failed: ${data.msg || 'Unknown error'}`);\n }\n return data.key;\n } catch (err) {\n clearTimeout(timeoutId);\n if (err instanceof DOMException && err.name === 'AbortError') {\n throw new Error('SASS key exchange timed out');\n }\n // Network-level failures (fetch rejects with a TypeError) are transient.\n if (err instanceof TypeError && attempt < retries) {\n lastError = err;\n continue;\n }\n throw err;\n }\n }\n\n throw lastError;\n}\n\n/** Build auth headers from a (possibly resolved) SASS key. */\nexport function buildAuthHeaders(auth: AuthConfig, resolvedSassKey?: string): AuthHeaders {\n const headers: AuthHeaders = {};\n\n switch (auth.mode) {\n case 'security-template':\n if (!resolvedSassKey) {\n throw new Error(\n '[dam-core] Cannot build auth headers for security-template mode: SASS key ' +\n 'exchange has not been performed. Call resolveAuth() first, or use sass-key mode.',\n );\n }\n headers['X-Filerobot-Key'] = resolvedSassKey;\n break;\n case 'sass-key':\n headers['X-Filerobot-Key'] = auth.sassKey;\n break;\n }\n\n if (auth.airboxPuid) {\n headers['X-Filerobot-Airbox-Puid'] = auth.airboxPuid;\n }\n return headers;\n}\n\n/** Narrow an unknown auth config to the asset-picker (drifted) shape. */\nfunction isAssetPickerAuth(auth: AnyAuthConfig): auth is AssetPickerAuth {\n return auth.mode === 'securityTemplate' || auth.mode === 'sassKey';\n}\n\n/**\n * Normalize any accepted auth shape onto the canonical {@link AuthConfig}. The canonical\n * shape passes through untouched; asset-picker's `projectToken`/camelCase shape is mapped.\n */\nexport function normalizeAuth(auth: AnyAuthConfig): AuthConfig {\n if (!isAssetPickerAuth(auth)) return auth;\n\n if (auth.mode === 'securityTemplate') {\n return {\n mode: 'security-template',\n container: auth.projectToken,\n securityTemplateId: auth.securityTemplateKey,\n airboxPuid: auth.airboxPuid,\n };\n }\n return {\n mode: 'sass-key',\n container: auth.projectToken,\n sassKey: auth.sassKey,\n airboxPuid: auth.airboxPuid,\n };\n}\n\n/**\n * Resolve any accepted auth shape into a ready-to-use base URL + headers.\n * Accepts the canonical {@link AuthConfig} or asset-picker's {@link AssetPickerAuth}.\n * For security-template mode this performs the SASS key exchange.\n */\nexport async function resolveAuth(auth: AnyAuthConfig, apiDomain?: string): Promise<ResolvedAuth> {\n const cfg = normalizeAuth(auth);\n const apiBase = getApiBase(cfg.container, apiDomain);\n\n if (cfg.mode === 'security-template') {\n const sassKey = await exchangeSassKey(cfg.container, cfg.securityTemplateId, apiDomain);\n return { apiBase, headers: buildAuthHeaders(cfg, sassKey), sassKey };\n }\n return { apiBase, headers: buildAuthHeaders(cfg) };\n}\n","// FUTURE @scaleflex/dam-core — REST client. See ../README.md.\n//\n// Unifies @scaleflex/asset-picker's `ApiClient` (request/post) with the PUT/DELETE\n// verbs light-dam needs for asset/folder mutations. Built on a resolved auth\n// (base URL + headers) so it stays decoupled from how auth was obtained.\nimport { resolveAuth } from '../auth';\nimport type { AuthConfig, AuthHeaders, ResolvedAuth } from '../auth';\n\nconst TIMEOUT_MS = 30_000;\n\ntype Params = Record<string, unknown>;\n\nexport class DamClient {\n /** REST base, e.g. \"https://api.filerobot.com/akli/v5\". */\n private readonly baseUrl: string;\n private headers: AuthHeaders;\n // Kept so a short-lived security-template key can be re-exchanged on 401.\n private readonly auth?: AuthConfig;\n private readonly apiDomain?: string;\n\n constructor(resolved: ResolvedAuth, auth?: AuthConfig, apiDomain?: string) {\n this.baseUrl = `${resolved.apiBase}/v5`;\n this.headers = resolved.headers;\n this.auth = auth;\n this.apiDomain = apiDomain;\n }\n\n /** Resolve an AuthConfig (performing key exchange if needed) and build a client. */\n static async create(auth: AuthConfig, apiDomain?: string): Promise<DamClient> {\n const resolved = await resolveAuth(auth, apiDomain);\n return new DamClient(resolved, auth, apiDomain);\n }\n\n get<T>(path: string, params?: Params): Promise<T> {\n const url = new URL(`${this.baseUrl}${path}`);\n if (params) {\n for (const [key, value] of Object.entries(params)) {\n if (value === undefined || value === null) continue;\n url.searchParams.set(key, Array.isArray(value) ? value.join(',') : String(value));\n }\n }\n return this.send<T>(url, { method: 'GET' });\n }\n\n post<T>(path: string, body: unknown): Promise<T> {\n return this.sendJson<T>('POST', path, body);\n }\n\n put<T>(path: string, body: unknown): Promise<T> {\n return this.sendJson<T>('PUT', path, body);\n }\n\n delete<T>(path: string, body?: unknown): Promise<T> {\n return this.sendJson<T>('DELETE', path, body);\n }\n\n /**\n * Absolute REST URL (incl. the `/v5` base) for a path — for building direct\n * browser-download links (e.g. the archive ZIP) that bypass `fetch`.\n */\n absoluteUrl(path: string): string {\n return `${this.baseUrl}${path}`;\n }\n\n /** The Filerobot key (`X-Filerobot-Key`), for query-param auth on direct downloads. */\n get authKey(): string | undefined {\n return (this.headers as Record<string, string>)['X-Filerobot-Key'];\n }\n\n private sendJson<T>(method: string, path: string, body: unknown): Promise<T> {\n return this.send<T>(new URL(`${this.baseUrl}${path}`), {\n method,\n headers: { 'Content-Type': 'application/json' },\n body: body === undefined ? undefined : JSON.stringify(body),\n });\n }\n\n private async send<T>(url: URL, init: RequestInit, retried = false): Promise<T> {\n const controller = new AbortController();\n const timeoutId = setTimeout(() => controller.abort(), TIMEOUT_MS);\n try {\n const response = await fetch(url.toString(), {\n ...init,\n headers: { ...this.headers, ...(init.headers as Record<string, string>) },\n signal: controller.signal,\n });\n clearTimeout(timeoutId);\n\n // A short-lived security-template key can expire mid-session — re-exchange\n // it once and retry, so the component recovers instead of 401-storming.\n if (response.status === 401 && !retried && this.auth?.mode === 'security-template') {\n const resolved = await resolveAuth(this.auth, this.apiDomain);\n this.headers = resolved.headers;\n return this.send<T>(url, init, true);\n }\n\n if (!response.ok) {\n let errorMsg = `API error: ${response.status} ${response.statusText}`;\n try {\n const body = await response.json();\n if (body?.msg) errorMsg = `API error: ${response.status} - ${body.msg}`;\n } catch {\n /* ignore parse errors */\n }\n throw new Error(errorMsg);\n }\n\n // 204 No Content (common for DELETE) has no body to parse.\n if (response.status === 204) return undefined as T;\n\n // Some gateways return an empty body on success (200/201) — tolerate it\n // instead of throwing a JSON parse error.\n const text = await response.text();\n if (!text) return undefined as T;\n const data = JSON.parse(text);\n if (data?.status === 'error') {\n throw new Error(`API error: ${data.msg || 'Unknown error'}`);\n }\n return data as T;\n } catch (err) {\n clearTimeout(timeoutId);\n if (err instanceof DOMException && err.name === 'AbortError') {\n throw new Error('API request timed out');\n }\n throw err;\n }\n }\n}\n","// FUTURE @scaleflex/dam-core — folders service. See ../README.md.\n//\n// Browse helpers (getFolders/createFolder) mirror @scaleflex/asset-picker.\n// rename/delete/move are light-dam's management layer, verified against\n// js-admin-react-filerobot-v5 (src/features/folders/folders.service.ts):\n// rename is `PUT /folder/{uuid}/name`; delete and move are bulk on `/folders`.\nimport type { DamClient } from './dam-client';\nimport type { Folder } from '../types/folder.types';\nimport type { GetFoldersResponse, RawFolder, ApiStatusResponse } from '../types/api.types';\n\nexport interface GetFoldersParams {\n folderPath?: string;\n recursive?: number;\n limit?: number;\n offset?: number;\n q?: string;\n sortBy?: 'created_at' | 'modified_at' | 'name';\n sortDirection?: 'asc' | 'desc';\n}\n\nfunction normalizeFolder(raw: RawFolder): Folder {\n const path = raw.path ?? raw.name;\n const parentPath = path.replace(/\\/[^/]*$/, '') || '/';\n return {\n uuid: raw.uuid,\n name: raw.name.split('/').pop() || raw.name,\n path,\n parentPath,\n createdAt: raw.created_at,\n modifiedAt: raw.modified_at,\n };\n}\n\nexport async function getFolders(\n client: DamClient,\n params: GetFoldersParams = {},\n): Promise<Folder[]> {\n const sortField = params.sortBy ?? 'name';\n const sortDir = params.sortDirection ?? 'asc';\n const res = await client.get<GetFoldersResponse>('/folders', {\n folder: params.folderPath ?? '/',\n recursive: params.recursive ?? 0,\n limit: params.limit ?? 1000,\n offset: params.offset ?? 0,\n q: params.q,\n sort: `${sortField}:${sortDir}`,\n });\n return (res.folders ?? []).map(normalizeFolder);\n}\n\n/**\n * Create a folder. The backend's `name` is a full path; we build it from\n * parent + leaf. `parentFolderPath` is expected to start with `/`.\n */\nexport async function createFolder(\n client: DamClient,\n leafName: string,\n parentFolderPath: string,\n): Promise<void> {\n const cleanParent = parentFolderPath.replace(/\\/+$/, '');\n const fullPath = cleanParent ? `${cleanParent}/${leafName}` : `/${leafName}`;\n const res = await client.post<ApiStatusResponse>('/folders', { name: fullPath });\n if (res.status !== 'success') throw new Error(res.msg || 'Failed to create folder');\n}\n\n// ── light-dam's management layer ─────────────────────────────────────────────\n\nexport async function renameFolder(\n client: DamClient,\n uuid: string,\n newName: string,\n): Promise<void> {\n const res = await client.put<ApiStatusResponse>(`/folder/${encodeURIComponent(uuid)}/name`, {\n name: newName,\n });\n if (res?.status === 'error') throw new Error(res.msg || 'Failed to rename folder');\n}\n\nexport async function deleteFolders(client: DamClient, uuids: string[]): Promise<void> {\n const res = await client.delete<ApiStatusResponse>('/folders', { folders_uuids: uuids });\n if (res?.status === 'error') throw new Error(res.msg || 'Failed to delete folders');\n}\n\nexport async function moveFolders(\n client: DamClient,\n uuids: string[],\n destinationPath: string,\n): Promise<void> {\n const res = await client.post<ApiStatusResponse>('/folders', {\n folders_uuids: uuids,\n new_folder_name: destinationPath,\n });\n if (res?.status === 'error') throw new Error(res.msg || 'Failed to move folders');\n}\n","// FUTURE @scaleflex/dam-core — files service. See ../README.md.\n//\n// getFiles mirrors @scaleflex/asset-picker. rename/delete/move are light-dam's\n// management layer, verified against js-admin-react-filerobot-v5.\nimport type { DamClient } from './dam-client';\nimport type { Asset, AssetType } from '../types/asset.types';\nimport type { GetFilesResponse, RawFile, ApiStatusResponse } from '../types/api.types';\n\n/** File-type categories the backend can filter on (`type:\"image\"` …). */\nexport type FileTypeFilter = 'image' | 'video' | 'audio' | 'document' | 'other';\n\n/** Coarse file-size buckets (in bytes), mapped to a `size:\"min..max\"` clause. */\nexport type SizeBucket = 'small' | 'medium' | 'large' | 'huge';\n\n/** \"Within the last …\" presets for the created/modified date facets. */\nexport type DatePreset = 'today' | 'week' | 'month' | 'year';\n\n/**\n * User-specified media-duration bounds in **whole seconds**, mapped to a\n * `video_duration:\"min..max\"` clause. `null` = open-ended on that side; both ends\n * are **inclusive** (unlike the half-open SIZE_BUCKETS, which tile contiguously).\n */\nexport interface DurationRange {\n min: number | null;\n max: number | null;\n}\n\n/** Size-bucket bounds in bytes; `null` = open-ended on that side. */\nconst SIZE_BUCKETS: Record<SizeBucket, [number | null, number | null]> = {\n small: [null, 1_048_576], // < 1 MB\n medium: [1_048_576, 10_485_760], // 1–10 MB\n large: [10_485_760, 104_857_600], // 10–100 MB\n huge: [104_857_600, null], // > 100 MB\n};\n\n/** Lookback window per date preset, in days from the start of today. */\nconst DATE_PRESET_DAYS: Record<DatePreset, number> = {\n today: 0,\n week: 7,\n month: 30,\n year: 365,\n};\n\n/**\n * Lower-bound date for a preset, anchored to UTC midnight. Shared by the\n * server-side `created>\"…\"` clause and the client-side favourites matcher so the\n * two never disagree, and stable across paged fetches within a day (a per-page\n * `Date.now()` would creep forward and skip/dup items at the boundary).\n */\nfunction sinceMidnight(preset: DatePreset, nowMs: number): Date {\n const d = new Date(nowMs);\n d.setUTCHours(0, 0, 0, 0);\n d.setUTCDate(d.getUTCDate() - DATE_PRESET_DAYS[preset]);\n return d;\n}\n\nexport interface GetFilesParams {\n folder?: string;\n recursive?: number;\n limit?: number;\n offset?: number;\n search?: string;\n q?: string;\n /** Restrict to these file-type categories (Filerobot `type:\"…\"` clause). */\n types?: FileTypeFilter[];\n /** Restrict to a file-size bucket (`size:\"min..max\"` clause). */\n sizeBucket?: SizeBucket;\n /** Restrict to a media-duration range in seconds (`video_duration:\"min..max\"` clause). */\n durationRange?: DurationRange;\n /** Only assets created within this window (`created>\"…\"` clause). */\n createdWithin?: DatePreset;\n /** Only assets modified within this window (`updated>\"…\"` clause). */\n updatedWithin?: DatePreset;\n sortBy?: 'created_at' | 'modified_at' | 'name' | 'size' | 'type';\n sortDirection?: 'asc' | 'desc';\n /** Additive computed fields; `cdn_permalink` must be requested explicitly. */\n fields?: string[];\n}\n\nconst DEFAULT_API_FIELDS = ['cdn_permalink'];\n\nconst EXT: Record<string, AssetType> = {};\nconst addExt = (type: AssetType, exts: string) => exts.split(' ').forEach((e) => (EXT[e] = type));\naddExt('image', 'jpg jpeg png gif webp svg avif bmp tif tiff heic ico');\naddExt('video', 'mp4 webm mov m4v avi mkv ogv');\naddExt('audio', 'mp3 wav ogg oga m4a aac flac');\naddExt('document', 'pdf doc docx xls xlsx ppt pptx txt csv rtf md');\naddExt('model', 'glb gltf obj fbx stl usdz ply 3ds dae');\n\nfunction toAssetType(raw: RawFile): AssetType {\n const ext = (raw.name.split('.').pop() || '').toLowerCase();\n // 3D models usually have a generic MIME, so extension wins for them.\n if (EXT[ext] === 'model') return 'model';\n\n const t = (raw.file_type || raw.type || '').toLowerCase();\n if (t.startsWith('image')) return 'image';\n if (t.startsWith('video')) return 'video';\n if (t.startsWith('audio')) return 'audio';\n if (t.includes('pdf') || t.includes('document') || t.includes('text')) return 'document';\n\n return EXT[ext] ?? 'other';\n}\n\nfunction normalizeFile(raw: RawFile): Asset {\n return {\n uuid: raw.uuid,\n name: raw.name,\n folder: raw.folder ?? '/',\n type: toAssetType(raw),\n fileType: raw.file_type ?? raw.type,\n cdnUrl: raw.cdn_permalink ?? raw.cdn_url,\n thumbnailUrl:\n raw.info?.video_thumbnail ??\n raw.info?.image_thumbnail ??\n raw.info?.thumbnail ??\n raw.info?.preview,\n // Live API returns `size: { bytes, pretty }`; tolerate a plain number too.\n size: typeof raw.size === 'object' ? raw.size?.bytes : raw.size,\n duration: raw.info?.video_duration ?? raw.info?.duration,\n width: raw.width,\n height: raw.height,\n createdAt: raw.created_at,\n modifiedAt: raw.modified_at,\n };\n}\n\nfunction buildFilesQuery(params: GetFilesParams): Record<string, unknown> {\n const fields = params.fields ?? DEFAULT_API_FIELDS;\n const sortField = params.sortBy ?? 'created_at';\n const sortDir = params.sortDirection ?? 'desc';\n\n const parts: string[] = [];\n if (params.search) parts.push(params.search);\n if (params.q) parts.push(params.q);\n // Facet clauses — same notation js-admin-react-filerobot-v5 emits, space-joined.\n if (params.types?.length) parts.push(`type:${params.types.map((t) => `\"${t}\"`).join(',')}`);\n if (params.sizeBucket) {\n const [min, max] = SIZE_BUCKETS[params.sizeBucket];\n parts.push(`size:\"${min ?? ''}..${max ?? ''}\"`);\n }\n if (params.durationRange) {\n const { min, max } = params.durationRange;\n // An all-null range constrains nothing — emit no clause rather than `video_duration:\"..\"`.\n if (min != null || max != null) parts.push(`video_duration:\"${min ?? ''}..${max ?? ''}\"`);\n }\n // Date facets are an after-date bound on created/updated (\"Today\" = since the\n // start of today, not a rolling 24h window — see sinceMidnight).\n const now = Date.now();\n if (params.createdWithin)\n parts.push(`created>\"${sinceMidnight(params.createdWithin, now).toISOString()}\"`);\n if (params.updatedWithin)\n parts.push(`updated>\"${sinceMidnight(params.updatedWithin, now).toISOString()}\"`);\n\n return {\n offset: params.offset ?? 0,\n limit: params.limit ?? 100,\n format: 'json,regvar:api,select:internal',\n preview: 2301, // Filerobot preview-size preset for thumbnails\n recursive: params.recursive ?? 1,\n fields: fields.length ? fields : undefined,\n sort: `${sortField}:${sortDir}`,\n folder: params.folder,\n q: parts.length ? parts.join(' ') : undefined,\n };\n}\n\nexport interface FilesPage {\n assets: Asset[];\n /** Total matching the query (from the API when available, else this page's size). */\n total: number;\n}\n\n/** One page of files. `total` is the API's grand total when provided, else this page's size. */\nexport async function getFilesPage(\n client: DamClient,\n params: GetFilesParams = {},\n): Promise<FilesPage> {\n const query = buildFilesQuery(params);\n const res = await client.get<GetFilesResponse>('/files', query);\n const assets = (res.files ?? []).map(normalizeFile);\n const total = typeof res.total === 'number' ? res.total : (res.count ?? assets.length);\n return { assets, total };\n}\n\nexport async function getFiles(client: DamClient, params: GetFilesParams = {}): Promise<Asset[]> {\n return (await getFilesPage(client, params)).assets;\n}\n\n/** Subset of the facets that can also be evaluated client-side (favourites). */\nexport type FacetFilters = Pick<\n GetFilesParams,\n 'types' | 'sizeBucket' | 'durationRange' | 'createdWithin' | 'updatedWithin'\n>;\n\n/** Our AssetType → the FileTypeFilter bucket the `type` facet selects on. */\nconst TYPE_TO_FILTER: Record<AssetType, FileTypeFilter> = {\n image: 'image',\n video: 'video',\n audio: 'audio',\n document: 'document',\n model: 'other', // no dedicated 'model' facet — folds into \"Other\"\n archive: 'other',\n font: 'other',\n other: 'other',\n};\n\n/**\n * Does an already-loaded asset satisfy the facet filters? Used for the\n * Favourites axis, which fetches a known uuid set directly and filters in\n * memory rather than re-querying the server. Mirrors buildFilesQuery's clauses\n * (shares SIZE_BUCKETS / sinceMidnight) so server- and client-side agree.\n */\nexport function assetMatchesFacets(asset: Asset, f: FacetFilters, nowMs: number): boolean {\n if (f.types?.length && !f.types.includes(TYPE_TO_FILTER[asset.type])) return false;\n if (f.sizeBucket) {\n const [min, max] = SIZE_BUCKETS[f.sizeBucket];\n if (asset.size == null) return false;\n if (min != null && asset.size < min) return false;\n if (max != null && asset.size >= max) return false;\n }\n if (f.durationRange) {\n const { min, max } = f.durationRange;\n // Undurated assets are excluded server-side too, so drop them here for parity.\n if (asset.duration == null) return false;\n // Inclusive on both ends — a user-entered range, not a contiguous bucket.\n if (min != null && asset.duration < min) return false;\n if (max != null && asset.duration > max) return false;\n }\n if (f.createdWithin) {\n const t = asset.createdAt ? Date.parse(asset.createdAt) : NaN;\n if (!(t >= sinceMidnight(f.createdWithin, nowMs).getTime())) return false;\n }\n if (f.updatedWithin) {\n const t = asset.modifiedAt ? Date.parse(asset.modifiedAt) : NaN;\n if (!(t >= sinceMidnight(f.updatedWithin, nowMs).getTime())) return false;\n }\n return true;\n}\n\ntype SortField = NonNullable<GetFilesParams['sortBy']>;\n\n/** Client-side sort matching the server's `sort=field:dir`, for the favourites set. */\nexport function sortAssets(assets: Asset[], sortBy: SortField, dir: 'asc' | 'desc'): Asset[] {\n const mult = dir === 'asc' ? 1 : -1;\n const key = (a: Asset): string | number => {\n switch (sortBy) {\n case 'name':\n return a.name?.toLowerCase() ?? '';\n case 'size':\n return a.size ?? 0;\n case 'type':\n return a.type;\n case 'modified_at':\n return a.modifiedAt ? Date.parse(a.modifiedAt) : 0;\n default: // created_at\n return a.createdAt ? Date.parse(a.createdAt) : 0;\n }\n };\n return [...assets].sort((a, b) => {\n const ka = key(a);\n const kb = key(b);\n return ka < kb ? -mult : ka > kb ? mult : 0;\n });\n}\n\n/**\n * Fetch specific files by uuid (one `GET /file/{uuid}` each, bounded concurrency).\n * The Favourites axis is a known, usually-small uuid set, so this is far cheaper\n * than scanning the whole library. Missing/forbidden uuids are skipped; results\n * keep the input order (caller re-sorts as needed).\n */\nexport async function getFilesByUuids(\n client: DamClient,\n uuids: string[],\n opts: { concurrency?: number; fields?: string[] } = {},\n): Promise<Asset[]> {\n const fields = opts.fields ?? DEFAULT_API_FIELDS;\n const out: Array<Asset | undefined> = new Array(uuids.length);\n let next = 0;\n const worker = async (): Promise<void> => {\n while (next < uuids.length) {\n const i = next++;\n try {\n const res = await client.get<{ file?: RawFile }>(`/file/${encodeURIComponent(uuids[i])}`, {\n format: 'json,regvar:api,select:internal',\n preview: 2301,\n fields: fields.length ? fields : undefined,\n });\n if (res?.file) out[i] = normalizeFile(res.file);\n } catch {\n /* a deleted/forbidden favourite just drops out of the list */\n }\n }\n };\n const lanes = Math.min(opts.concurrency ?? 8, uuids.length);\n await Promise.all(Array.from({ length: lanes }, worker));\n return out.filter((a): a is Asset => a !== undefined);\n}\n\n// ── light-dam's management layer ─────────────────────────────────────────────\n// Endpoint shapes verified against js-admin-react-filerobot-v5\n// (src/features/files/files.service.ts): rename is per-file; delete and move are\n// bulk (uuid arrays in the body). The leading path segment is singular `/file`\n// for rename, plural `/files` for the bulk operations — matching the backend.\n\nexport async function renameFile(client: DamClient, uuid: string, newName: string): Promise<void> {\n const res = await client.put<ApiStatusResponse>(`/file/${encodeURIComponent(uuid)}/name`, {\n name: newName,\n });\n if (res?.status === 'error') throw new Error(res.msg || 'Failed to rename file');\n}\n\nexport async function deleteFiles(client: DamClient, uuids: string[]): Promise<void> {\n const res = await client.delete<ApiStatusResponse>('/files', { files_uuids: uuids });\n if (res?.status === 'error') throw new Error(res.msg || 'Failed to delete files');\n}\n\nexport async function moveFiles(\n client: DamClient,\n uuids: string[],\n destinationFolder: string,\n): Promise<void> {\n const res = await client.post<ApiStatusResponse>('/files', {\n files_uuids: uuids,\n new_folder_name: destinationFolder,\n });\n if (res?.status === 'error') throw new Error(res.msg || 'Failed to move files');\n}\n","// FUTURE @scaleflex/dam-core — labels service. See ../README.md.\n// Ported from @scaleflex/asset-picker. Labels are a cross-folder navigation axis;\n// they are simple tags (NOT metadata) so they fit within \"light\".\nimport type { DamClient } from './dam-client';\nimport type { Label } from '../types/label.types';\nimport type { ApiStatusResponse } from '../types/api.types';\n\ninterface GetLabelsResponse extends ApiStatusResponse {\n labels: Label[];\n}\n\nexport async function getLabels(client: DamClient): Promise<Label[]> {\n const res = await client.get<GetLabelsResponse>('/labels');\n return res.labels ?? [];\n}\n\n/**\n * Build the Files API query that filters by a label, mirroring asset-picker:\n * `q=labels:{sid}` with the leading \"#\" stripped from the label's sid.\n * Returns undefined for a label with no usable sid (caller skips the filter).\n */\nexport function labelFilterQuery(label: Label): string | undefined {\n if (!label.sid) return undefined;\n return `labels:${label.sid.replace(/^#/, '')}`;\n}\n","// @scaleflex/dam-core — file-variations assembly (UI-free, pure).\n//\n// Turns the raw `/variants` list + the container presets + the asset itself into a\n// single ordered `Variation[]` for the preview's Variations tab. Classification\n// mirrors js-admin-react-filerobot-v5 / portals-fe:\n// • Original — synthesized from the asset.\n// • Custom variants — `/variants` rows whose name is NOT a preset and that are not bg-removed.\n// • Background-removed — `/variants` rows with info.remove_background (kept, not preset-named).\n// • Preset variations — one virtual row per container preset, applied as `?p=<name>`.\n\nimport type { Asset } from '../types/asset.types';\nimport type { Variation, Preset } from '../types/variation.types';\nimport type { RawVariation } from '../types/api.types';\n\n/**\n * Apply a container preset to an original CDN URL by setting `?p=<name>` (replacing\n * any existing `p`). Returns the input unchanged if it isn't a valid URL.\n */\nexport function presetUrl(originalUrl: string, presetName: string): string {\n try {\n const url = new URL(originalUrl);\n url.searchParams.set('p', presetName);\n return url.toString();\n } catch {\n return originalUrl;\n }\n}\n\n/** Normalize one raw `/variants` row into a `Variation` (custom or background-removed). */\nexport function normalizeVariation(raw: RawVariation): Variation {\n return {\n uuid: raw.uuid,\n name: raw.name,\n url: raw.url,\n previewUrl: raw.preview ?? raw.url,\n width: raw.info?.img_w,\n height: raw.info?.img_h,\n size: raw.info?.size,\n format: raw.info?.img_type,\n isOriginal: false,\n isPreset: false,\n bgRemoved: raw.info?.remove_background === true,\n };\n}\n\n/** The Original variation, synthesized from the asset. */\nfunction originalVariation(asset: Asset): Variation {\n return {\n uuid: asset.uuid,\n name: asset.name,\n url: asset.cdnUrl ?? '',\n previewUrl: asset.thumbnailUrl ?? asset.cdnUrl,\n width: asset.width,\n height: asset.height,\n size: asset.size,\n format: asset.fileType,\n isOriginal: true,\n isPreset: false,\n bgRemoved: false,\n };\n}\n\n/** A virtual preset variation, derived from the asset's original URL. */\nfunction presetVariation(asset: Asset, preset: Preset): Variation {\n const base = asset.cdnUrl ?? '';\n return {\n uuid: `preset-${preset.name}`,\n name: preset.name,\n url: presetUrl(base, preset.name),\n previewUrl: presetUrl(asset.thumbnailUrl ?? base, preset.name),\n isOriginal: false,\n isPreset: true,\n bgRemoved: false,\n };\n}\n\nexport interface AssembleVariationsInput {\n asset: Asset;\n /** Raw rows from `getFileVariations`. */\n variants?: RawVariation[];\n /** Container presets from `getPresets`. */\n presets?: Preset[];\n}\n\n/**\n * Assemble the full Variations list: `[Original, …custom, …background-removed, …presets]`.\n * Preset-named raw variants are excluded from the custom set (they're represented by the\n * virtual preset rows instead), matching the reference admins' behaviour.\n */\nexport function assembleVariations({\n asset,\n variants = [],\n presets = [],\n}: AssembleVariationsInput): Variation[] {\n const presetNames = new Set(presets.map((p) => p.name));\n\n const custom = variants\n .filter((v) => !presetNames.has(v.name) && v.info?.remove_background !== true)\n .map(normalizeVariation);\n\n const bgRemoved = variants\n .filter((v) => v.info?.remove_background === true && !presetNames.has(v.name))\n .map(normalizeVariation);\n\n const presetRows = presets.map((p) => presetVariation(asset, p));\n\n return [originalVariation(asset), ...custom, ...bgRemoved, ...presetRows];\n}\n","// @scaleflex/dam-core — file variations + container presets service.\n// Endpoints verified against js-admin-react-filerobot-v5 / portals-fe:\n// GET /v5/file/{uuid}/variants → { variants: RawVariation[] }\n// GET /v5/settings → container config incl. cloudimg presets\nimport type { DamClient } from './dam-client';\nimport type { Asset } from '../types/asset.types';\nimport type { Variation, Preset } from '../types/variation.types';\nimport type {\n GetVariationsResponse,\n GetSettingsResponse,\n RawVariation,\n RawPreset,\n} from '../types/api.types';\nimport { assembleVariations } from '../helpers/variations';\n\n/** List a file's raw variations (custom + background-removed renditions). */\nexport async function getFileVariations(client: DamClient, uuid: string): Promise<RawVariation[]> {\n const res = await client.get<GetVariationsResponse>(\n `/file/${encodeURIComponent(uuid)}/variants`,\n );\n return res?.variants ?? [];\n}\n\n/** Normalize the various preset shapes (`string` | `{ name, desc }`) into `Preset[]`. */\nexport function normalizePresets(raw: RawPreset[] | undefined): Preset[] {\n if (!Array.isArray(raw)) return [];\n return raw\n .map((p): Preset | null =>\n typeof p === 'string'\n ? { name: p }\n : p && typeof p.name === 'string'\n ? { name: p.name, desc: p.desc }\n : null,\n )\n .filter((p): p is Preset => p !== null);\n}\n\n/**\n * Fetch the container's resolution presets from `/settings`. The presets array\n * lives under the container's cloudimg config; its exact nesting varies by\n * deployment, so we probe the known locations defensively (verify against a live\n * container — Q-AP presets).\n */\nexport async function getPresets(client: DamClient): Promise<Preset[]> {\n const res = await client.get<GetSettingsResponse>('/settings');\n const raw =\n res?.presets ?? res?.cloudimg?.presets ?? res?.settings?.cloudimg?.presets ?? undefined;\n return normalizePresets(raw);\n}\n\n/**\n * Convenience: fetch a file's variants + the container presets and assemble the\n * full ordered `Variation[]` (Original + custom + background-removed + presets).\n * Hosts that already hold variants/presets can call `assembleVariations` directly\n * instead (the fetch-or-inject contract).\n */\nexport async function loadVariations(client: DamClient, asset: Asset): Promise<Variation[]> {\n const [variants, presets] = await Promise.all([\n getFileVariations(client, asset.uuid),\n getPresets(client),\n ]);\n return assembleVariations({ asset, variants, presets });\n}\n","// @scaleflex/dam-core — server-side ZIP/bundle export (async archive job).\n// Contract verified against js-admin-react-filerobot-v5 (archive.thunks.js):\n// POST /file-archive { files_uuids, folders_uuids, urls } → { task_id }\n// GET /file-archive/{id}?status_only=1 → { task_status, task_percent }\n// GET /file-archive/{id}?secret_key=<key> → application/zip (after DONE)\n//\n// dam-core builds the URL + drives the poll; triggering the browser download (an\n// <a> click) stays in the UI layer (it is a DOM concern, and dam-core is UI-free).\n//\n// NOTE (Q-AP-ZIP): the path is built relative to the client's `/v5` base. Confirm\n// `file-archive` is reachable there for the target container — some deployments\n// expose it off the version-less root. Verify against a live container.\nimport type { DamClient } from './dam-client';\nimport type { CreateArchiveResponse, ArchiveStatusResponse } from '../types/api.types';\n\n/** What to bundle. Any combination of file uuids, folder uuids, and raw CDN URLs. */\nexport interface ArchiveTarget {\n filesUuids?: string[];\n foldersUuids?: string[];\n urls?: string[];\n}\n\n/** Kick off an archive job; resolves with its task id. */\nexport async function createArchive(client: DamClient, target: ArchiveTarget): Promise<string> {\n const res = await client.post<CreateArchiveResponse>('/file-archive', {\n files_uuids: target.filesUuids ?? [],\n folders_uuids: target.foldersUuids ?? [],\n urls: target.urls ?? [],\n });\n if (!res?.task_id) throw new Error('Archive task was not created');\n return res.task_id;\n}\n\n/** One status read of an archive job. */\nexport function getArchiveStatus(\n client: DamClient,\n taskId: string,\n): Promise<ArchiveStatusResponse> {\n return client.get<ArchiveStatusResponse>(`/file-archive/${encodeURIComponent(taskId)}`, {\n status_only: 1,\n });\n}\n\nconst IN_PROGRESS = new Set(['PENDING', 'RUNNING', 'PROCESSING', '']);\n\n/**\n * Interpret an archive status payload. Pure — the heart of the poll loop, tested\n * in isolation. Throws on a terminal failure status; returns `{done,progress}`\n * otherwise (`done` only for DONE).\n */\nexport function archiveStatusOutcome(res: ArchiveStatusResponse): {\n done: boolean;\n progress: number;\n} {\n const status = (res?.task_status ?? '').toUpperCase();\n const progress = typeof res?.task_percent === 'number' ? res.task_percent : 0;\n if (status === 'DONE') return { done: true, progress: 100 };\n if (IN_PROGRESS.has(status)) return { done: false, progress };\n throw new Error(`Archive job failed: ${res?.task_status ?? 'unknown status'}`);\n}\n\nexport interface PollArchiveOptions {\n /** Delay between status reads (default 1500ms). */\n intervalMs?: number;\n /** Give up after this long (default 120000ms). */\n timeoutMs?: number;\n /** Tolerate this many transient status-read errors (the job may not be registered yet). */\n maxRetries?: number;\n /** Abort the poll early. */\n signal?: AbortSignal;\n /** Progress callback (0–100). */\n onProgress?: (percent: number) => void;\n /** Injectable sleep (tests pass an immediate resolver). */\n sleep?: (ms: number) => Promise<void>;\n /** Injectable clock (tests pass a controllable one). */\n now?: () => number;\n}\n\n/** Poll an archive job until it reports DONE (or throws on failure/timeout/abort). */\nexport async function pollArchive(\n client: DamClient,\n taskId: string,\n opts: PollArchiveOptions = {},\n): Promise<void> {\n const interval = opts.intervalMs ?? 1500;\n const timeout = opts.timeoutMs ?? 120_000;\n const maxRetries = opts.maxRetries ?? 5;\n const sleep = opts.sleep ?? ((ms) => new Promise<void>((r) => setTimeout(r, ms)));\n const now = opts.now ?? Date.now;\n\n const deadline = now() + timeout;\n let retries = 0;\n\n for (;;) {\n if (opts.signal?.aborted) throw new Error('Archive polling aborted');\n if (now() > deadline) throw new Error('Archive polling timed out');\n\n let res: ArchiveStatusResponse;\n try {\n res = await getArchiveStatus(client, taskId);\n } catch (err) {\n // The job can lag behind its task id (404) or hit a transient blip — retry a bounded number of times.\n if (retries++ < maxRetries) {\n await sleep(interval);\n continue;\n }\n throw err;\n }\n\n const { done, progress } = archiveStatusOutcome(res);\n opts.onProgress?.(progress);\n if (done) return;\n await sleep(interval);\n }\n}\n\n/** Build the authenticated direct-download URL for a finished archive. */\nexport function archiveDownloadUrl(client: DamClient, taskId: string): string {\n const base = client.absoluteUrl(`/file-archive/${encodeURIComponent(taskId)}`);\n const key = client.authKey;\n if (!key) return base;\n const sep = base.includes('?') ? '&' : '?';\n return `${base}${sep}secret_key=${encodeURIComponent(key)}`;\n}\n\n/** Create → poll → resolve the authenticated download URL. */\nexport async function createAndPollArchive(\n client: DamClient,\n target: ArchiveTarget,\n opts: PollArchiveOptions = {},\n): Promise<string> {\n const taskId = await createArchive(client, target);\n await pollArchive(client, taskId, opts);\n return archiveDownloadUrl(client, taskId);\n}\n","// @scaleflex/dam-core — raw Filerobot browse endpoints.\n//\n// The \"raw\" counterpart to files.service / folders.service: these return the Filerobot wire\n// response un-normalized (FilerobotAsset / FilerobotFolder), for consumers that need full\n// fidelity — `@scaleflex/asset-picker` returns the raw object on select. Query-building mirrors\n// what asset-picker shipped (verified against the live API); the normalized getFilesPage /\n// getFolders remain the right choice for the headless core + light-dam. See plan/12 Wave 3.\nimport type { DamClient } from './dam-client';\nimport type { FilerobotAsset, FilerobotFolder } from '../types/filerobot.types';\n\n/** Computed Files API fields requested by default (signed CDN permalink). Additive on the wire. */\nconst DEFAULT_API_FIELDS = ['cdn_permalink'];\n\nexport interface FilerobotFilesParams {\n folder?: string;\n offset?: number;\n limit?: number;\n sort_by?: string;\n sort_direction?: string;\n /** Free-text search. */\n search?: string;\n /** Pre-serialized search/filter notation. */\n q?: string;\n recursive?: number;\n /** Enable AI-powered semantic search. */\n with_ai?: boolean;\n ai_query?: string;\n ai_lang?: string;\n /** Filter by collection. */\n collection_uuid?: string;\n /** Base64-encoded collection-folder filter. */\n f64?: string;\n /** Extra computed fields (e.g. `cdn_permalink`); undefined → the default set. */\n fields?: string[];\n /** Project-level toggle — `cdn_permalink` is stripped from `fields` when false. */\n permalinksEnabled?: boolean;\n}\n\nexport interface FilerobotFilesResponse {\n status: string;\n msg?: string;\n files: FilerobotAsset[];\n folders: FilerobotFolder[];\n base?: {\n count?: {\n files_direct?: number;\n files_recursive?: number;\n };\n };\n info?: {\n total_files_count?: number;\n total_files_size?: number;\n };\n limit: number;\n offset: number;\n}\n\nconst SORT_FIELDS: Record<string, string> = {\n created_at: 'created_at',\n modified_at: 'modified_at',\n name: 'name',\n size: 'size',\n type: 'type',\n};\n\nfunction buildFilesQuery(params: FilerobotFilesParams): Record<string, unknown> {\n let fields = params.fields ?? DEFAULT_API_FIELDS;\n if (!params.permalinksEnabled) {\n fields = fields.filter((field) => field !== 'cdn_permalink');\n }\n const apiParams: Record<string, unknown> = {\n offset: params.offset ?? 0,\n limit: params.limit ?? 100,\n format: 'json,regvar:api,select:internal',\n preview: 2301,\n recursive: params.recursive ?? 1,\n };\n if (fields.length > 0) apiParams.fields = fields;\n\n const sortField = params.sort_by ?? 'created_at';\n const sortDir = params.sort_direction ?? 'desc';\n apiParams.sort = `${SORT_FIELDS[sortField] || sortField}:${sortDir}`;\n\n if (params.folder) apiParams.folder = params.folder;\n\n // With AI search active the text goes in ai_query, not q.\n const parts: string[] = [];\n if (params.search && !params.with_ai) parts.push(params.search);\n if (params.q) parts.push(params.q);\n if (parts.length > 0) apiParams.q = parts.join(' ');\n\n if (params.with_ai) {\n apiParams.with_ai = true;\n if (params.ai_query) apiParams.ai_query = params.ai_query;\n if (params.ai_lang) apiParams.ai_lang = params.ai_lang;\n }\n if (params.collection_uuid) apiParams.collection_uuid = params.collection_uuid;\n if (params.f64) apiParams.f64 = params.f64;\n\n return apiParams;\n}\n\n/** Fetch a page of files as the raw Filerobot wire response (no normalization). */\nexport function getFilesRaw(\n client: DamClient,\n params: FilerobotFilesParams,\n): Promise<FilerobotFilesResponse> {\n return client.get<FilerobotFilesResponse>('/files', buildFilesQuery(params));\n}\n\nexport interface FilerobotFilesStatsResponse {\n status: string;\n stats?: {\n approx_files_count?: number;\n approx_files_size?: number;\n };\n info?: {\n total_files_count?: number;\n total_files_size?: number;\n };\n}\n\nexport type FilerobotFilesStatsParams = Pick<\n FilerobotFilesParams,\n 'folder' | 'q' | 'search' | 'recursive' | 'with_ai' | 'ai_query' | 'ai_lang' | 'collection_uuid' | 'f64'\n>;\n\n/** Approximate file count/size for a scope (the `/files/stats` endpoint). */\nexport function getFilesStats(\n client: DamClient,\n params: FilerobotFilesStatsParams,\n): Promise<FilerobotFilesStatsResponse> {\n const apiParams: Record<string, unknown> = { recursive: params.recursive ?? 1 };\n if (params.folder) apiParams.folder = params.folder;\n const parts: string[] = [];\n if (params.search && !params.with_ai) parts.push(params.search);\n if (params.q) parts.push(params.q);\n if (parts.length > 0) apiParams.q = parts.join(' ');\n if (params.with_ai) {\n apiParams.with_ai = true;\n if (params.ai_query) apiParams.ai_query = params.ai_query;\n if (params.ai_lang) apiParams.ai_lang = params.ai_lang;\n }\n if (params.collection_uuid) apiParams.collection_uuid = params.collection_uuid;\n if (params.f64) apiParams.f64 = params.f64;\n return client.get<FilerobotFilesStatsResponse>('/files/stats', apiParams);\n}\n\nexport interface FilerobotFoldersParams {\n folderPath?: string;\n recursive?: number;\n limit?: number;\n offset?: number;\n q?: string;\n sort_by?: string;\n sort_direction?: string;\n}\n\nexport interface FilerobotFoldersResponse {\n status: string;\n msg?: string;\n folders: FilerobotFolder[];\n total?: number;\n}\n\n/** Fetch folders as the raw Filerobot wire response (no normalization). */\nexport function getFoldersRaw(\n client: DamClient,\n params: FilerobotFoldersParams = {},\n): Promise<FilerobotFoldersResponse> {\n const apiParams: Record<string, unknown> = {\n recursive: params.recursive ?? 0,\n folder: params.folderPath ?? '/',\n limit: params.limit ?? 1000,\n offset: params.offset ?? 0,\n };\n if (params.q) apiParams.q = params.q;\n const sortField = params.sort_by ?? 'created_at';\n const sortDir = params.sort_direction ?? 'asc';\n apiParams.sort = `${SORT_FIELDS[sortField] || sortField}:${sortDir}`;\n return client.get<FilerobotFoldersResponse>('/folders', apiParams);\n}\n\nexport interface FolderPreview {\n file_uuid: string;\n file_uri_cdn: string;\n file_type: string;\n preview_origin: string;\n}\n\ninterface FolderPreviewsResponse {\n status: string;\n folders: Record<string, FolderPreview[]>;\n}\n\n/** Preview thumbnails for a set of folders (`POST /folders/previews`). */\nexport async function getFolderPreviews(\n client: DamClient,\n folderUuids: string[],\n): Promise<Record<string, FolderPreview[]>> {\n if (folderUuids.length === 0) return {};\n const res = await client.post<FolderPreviewsResponse>('/folders/previews', {\n folders: folderUuids,\n });\n return res.folders || {};\n}\n","// FUTURE @scaleflex/dam-core — headless browse store. See ../README.md.\n//\n// Framework-agnostic observable (subscribe/getState/actions) — ZERO Lit/React.\n// The UI layer adapts it (e.g. light-dam's Lit ReactiveController). Holds the\n// whole view/scope model documented in the repo root CLAUDE.md (§Views & UI model):\n// ONE content view (Assets) navigated by scope axes (folder | label | favourites).\nimport type { DamClient } from '../api/dam-client';\nimport {\n getFolders,\n createFolder,\n renameFolder,\n deleteFolders,\n moveFolders,\n} from '../api/folders.service';\nimport {\n getFilesPage,\n getFilesByUuids,\n assetMatchesFacets,\n sortAssets,\n renameFile,\n deleteFiles,\n moveFiles,\n} from '../api/files.service';\nimport type {\n FileTypeFilter,\n SizeBucket,\n DurationRange,\n DatePreset,\n FacetFilters,\n} from '../api/files.service';\nimport { getLabels, labelFilterQuery } from '../api/labels.service';\nimport type { Asset } from '../types/asset.types';\nimport type { Folder } from '../types/folder.types';\nimport type { Label } from '../types/label.types';\n\nexport type ScopeType = 'folder' | 'label' | 'favourites' | 'all';\nexport interface Scope {\n type: ScopeType;\n /** Folder path, label uuid, etc. — unused for 'favourites' / 'all'. */\n value?: string;\n}\n\nexport type ViewMode = 'grid' | 'list';\nexport type GridSize = 's' | 'm' | 'l';\nexport type SortBy = 'created_at' | 'modified_at' | 'name' | 'size' | 'type';\nexport type SortDirection = 'asc' | 'desc';\n\n/** Assets fetched per page; infinite scroll appends one page at a time. */\nexport const PAGE_SIZE = 100;\n\n/** Cap on favourites fetched by uuid, so a huge starred set can't fan out unbounded. */\nexport const MAX_FAVOURITES = 1000;\n\n/** The filter facets, as one object so adding a facet is a single-place change. */\nexport interface BrowseFilters {\n /** File-type categories (empty = all). */\n types: FileTypeFilter[];\n /** File-size bucket (null = any size). */\n size: SizeBucket | null;\n /** Media-duration range in seconds (null = any duration). */\n duration: DurationRange | null;\n /** \"Uploaded within\" on created date (null = any time). */\n created: DatePreset | null;\n /** \"Updated within\" on modified date (null = any time). */\n updated: DatePreset | null;\n}\n\nconst EMPTY_FILTERS: BrowseFilters = {\n types: [],\n size: null,\n duration: null,\n created: null,\n updated: null,\n};\n\nexport interface BrowseState {\n scope: Scope;\n viewMode: ViewMode;\n gridSize: GridSize;\n sortBy: SortBy;\n sortDirection: SortDirection;\n searchQuery: string;\n /** Active filter facets (type / size / duration / created / updated). */\n filters: BrowseFilters;\n folders: Folder[];\n labels: Label[];\n assets: Asset[];\n /** Total assets matching the current scope (from the API when available). */\n total: number;\n /** True when the Favourites scan stopped at its safety cap (rare). */\n truncated: boolean;\n /** True when more assets exist than are loaded — infinite scroll can fetch the next page. */\n hasMore: boolean;\n /** True while a `loadMore()` page is in flight (distinct from the initial `loading`). */\n loadingMore: boolean;\n /** Lazy folder-tree cache: folder path → its child folders. */\n treeChildren: Record<string, Folder[]>;\n /** Folder paths currently expanded in the sidebar tree. */\n expandedFolders: ReadonlySet<string>;\n /** Selected asset uuids — single or multi. */\n selection: ReadonlySet<string>;\n /** Favourited asset uuids (the per-user axis) — for star state in any scope. */\n favourites: ReadonlySet<string>;\n loading: boolean;\n error: string | null;\n}\n\nexport interface BrowseStoreOptions {\n rootFolder?: string;\n viewMode?: ViewMode;\n gridSize?: GridSize;\n sortBy?: SortBy;\n sortDirection?: SortDirection;\n /** Resolves favourite asset uuids (the only per-user axis). */\n favourites?: FavouritesAdapter;\n /** Called whenever the scope changes — lets the UI emit outward events. */\n onScopeChange?: (scope: Scope) => void;\n}\n\n/**\n * Pluggable persistence for the (per-user) Favourites axis.\n *\n * Filerobot v5 DOES have a native per-user favourites API\n * (`POST`/`DELETE /users/favorites` with `{ files_uuids }`), but light-dam keeps\n * this adapter so it doesn't bake in a user-identity/auth assumption: the default\n * is localStorage, and hosts (Portals/Spotlight) can inject an adapter backed by\n * the native API or their own store. Lives in core so it graduates to dam-core.\n */\nexport interface FavouritesAdapter {\n list(): Promise<string[]> | string[];\n add(uuid: string): Promise<void> | void;\n remove(uuid: string): Promise<void> | void;\n has(uuid: string): Promise<boolean> | boolean;\n}\n\ntype Listener = (state: BrowseState) => void;\n\nexport class BrowseStore {\n private state: BrowseState;\n private readonly listeners = new Set<Listener>();\n private readonly favAdapter?: FavouritesAdapter;\n private readonly onScopeChange?: (scope: Scope) => void;\n private loadSeq = 0;\n private treeSeq = 0;\n /** Raw count of assets fetched for the current scope — the infinite-scroll offset cursor. */\n private fetchedCount = 0;\n /** Last individually-selected asset — the pivot for shift-click range selection. */\n private selectionAnchor: string | null = null;\n\n constructor(\n private readonly client: DamClient,\n opts: BrowseStoreOptions = {},\n ) {\n this.favAdapter = opts.favourites;\n this.onScopeChange = opts.onScopeChange;\n this.state = {\n scope: { type: 'folder', value: opts.rootFolder ?? '/' },\n viewMode: opts.viewMode ?? 'grid',\n gridSize: opts.gridSize ?? 'm',\n sortBy: opts.sortBy ?? 'created_at',\n sortDirection: opts.sortDirection ?? 'desc',\n searchQuery: '',\n filters: { ...EMPTY_FILTERS },\n folders: [],\n labels: [],\n assets: [],\n total: 0,\n truncated: false,\n hasMore: false,\n loadingMore: false,\n treeChildren: {},\n expandedFolders: new Set(),\n selection: new Set(),\n favourites: new Set(),\n loading: false,\n error: null,\n };\n }\n\n /** Load sidebar metadata (labels, favourites, root folder tree) then the scope. */\n async init(): Promise<void> {\n await Promise.all([this.loadLabels(), this.loadFavourites(), this.loadFolderChildren('/')]);\n await this.load();\n }\n\n // ── folder tree (lazy) ──────────────────────────────────────────────────────\n\n /** Fetch and cache a folder's children (no-op if already cached). */\n async loadFolderChildren(path: string): Promise<void> {\n if (this.state.treeChildren[path]) return;\n try {\n const children = await getFolders(this.client, { folderPath: path });\n this.set({ treeChildren: { ...this.state.treeChildren, [path]: children } });\n } catch {\n /* tree is non-fatal; node just won't expand */\n }\n }\n\n /** Expand/collapse a folder node, lazy-loading its children on first expand. */\n async toggleFolder(path: string): Promise<void> {\n const expanded = new Set(this.state.expandedFolders);\n if (expanded.has(path)) {\n expanded.delete(path);\n this.set({ expandedFolders: expanded });\n } else {\n await this.loadFolderChildren(path);\n expanded.add(path);\n this.set({ expandedFolders: expanded });\n }\n }\n\n private async loadLabels(): Promise<void> {\n try {\n this.set({ labels: await getLabels(this.client) });\n } catch {\n /* labels are non-fatal; sidebar just omits them */\n }\n }\n\n private async loadFavourites(): Promise<void> {\n if (!this.favAdapter) return;\n try {\n this.set({ favourites: new Set(await this.favAdapter.list()) });\n } catch {\n /* ignore — favourites are best-effort */\n }\n }\n\n /** Toggle an asset's favourite status (per-user, via the adapter). */\n async toggleFavourite(uuid: string): Promise<void> {\n const isFav = this.state.favourites.has(uuid);\n const optimistic = new Set(this.state.favourites);\n if (isFav) optimistic.delete(uuid);\n else optimistic.add(uuid);\n this.set({ favourites: optimistic });\n try {\n if (this.favAdapter) {\n if (isFav) await this.favAdapter.remove(uuid);\n else await this.favAdapter.add(uuid);\n }\n } catch {\n // Revert ONLY this uuid against the current set, so a concurrent toggle of\n // another asset isn't clobbered.\n const reverted = new Set(this.state.favourites);\n if (isFav) reverted.add(uuid);\n else reverted.delete(uuid);\n this.set({ favourites: reverted });\n return;\n }\n if (this.state.scope.type === 'favourites') void this.load();\n }\n\n getState(): BrowseState {\n return this.state;\n }\n\n subscribe(listener: Listener): () => void {\n this.listeners.add(listener);\n return () => this.listeners.delete(listener);\n }\n\n private set(patch: Partial<BrowseState>): void {\n this.state = { ...this.state, ...patch };\n for (const l of this.listeners) l(this.state);\n }\n\n // ── navigation / scope ─────────────────────────────────────────────────────\n\n setScope(scope: Scope): void {\n this.selectionAnchor = null;\n this.set({ scope, selection: new Set() });\n this.onScopeChange?.(scope);\n void this.load();\n }\n\n openFolder(path: string): void {\n this.setScope({ type: 'folder', value: path });\n }\n\n openLabel(uuid: string): void {\n this.setScope({ type: 'label', value: uuid });\n }\n\n openFavourites(): void {\n this.setScope({ type: 'favourites' });\n }\n\n // ── view controls ──────────────────────────────────────────────────────────\n\n setViewMode(viewMode: ViewMode): void {\n this.set({ viewMode });\n }\n\n setGridSize(gridSize: GridSize): void {\n this.set({ gridSize });\n }\n\n setSort(sortBy: SortBy, sortDirection: SortDirection): void {\n this.set({ sortBy, sortDirection });\n void this.load();\n }\n\n setSearch(searchQuery: string): void {\n this.set({ searchQuery });\n void this.load();\n }\n\n /** Merge a partial facet patch into the active filters and reload from page 1. */\n setFilters(patch: Partial<BrowseFilters>): void {\n this.set({ filters: { ...this.state.filters, ...patch } });\n void this.load();\n }\n\n /** Toggle one file-type in the type facet. Reloads from page 1. */\n toggleType(type: FileTypeFilter): void {\n const types = this.state.filters.types;\n this.setFilters({\n types: types.includes(type) ? types.filter((t) => t !== type) : [...types, type],\n });\n }\n\n /** Number of active facets across all groups (for the toolbar badge). */\n get activeFilterCount(): number {\n const { types, size, duration, created, updated } = this.state.filters;\n return (\n types.length + (size ? 1 : 0) + (duration ? 1 : 0) + (created ? 1 : 0) + (updated ? 1 : 0)\n );\n }\n\n /** Clear every facet in one update, then reload. No-op if nothing is active. */\n clearFilters(): void {\n if (!this.activeFilterCount) return;\n this.set({ filters: { ...EMPTY_FILTERS } });\n void this.load();\n }\n\n // ── selection ──────────────────────────────────────────────────────────────\n\n toggleSelect(uuid: string, multi = false): void {\n const next = new Set(multi ? this.state.selection : []);\n const willSelect = !next.has(uuid);\n if (willSelect) next.add(uuid);\n else next.delete(uuid);\n // A selecting click becomes the pivot for a subsequent shift-click range; a\n // deselecting (toggle-off) click leaves the pivot put so it never points at an\n // item that's no longer selected.\n if (willSelect) this.selectionAnchor = uuid;\n this.set({ selection: next });\n }\n\n /**\n * Shift-click range selection: add every asset between the anchor (the last\n * individually-selected item) and `uuid` — in the current display order — to\n * the selection. Additive: existing selections are preserved, and the anchor\n * stays put so the range can be re-adjusted with another shift-click. With no\n * anchor yet, behaves like a plain multi-select toggle.\n */\n selectRange(uuid: string): void {\n const assets = this.state.assets;\n const toIdx = assets.findIndex((a) => a.uuid === uuid);\n if (toIdx < 0) return;\n const anchorIdx =\n this.selectionAnchor != null ? assets.findIndex((a) => a.uuid === this.selectionAnchor) : -1;\n if (anchorIdx < 0) {\n this.toggleSelect(uuid, true);\n return;\n }\n const [start, end] = anchorIdx <= toIdx ? [anchorIdx, toIdx] : [toIdx, anchorIdx];\n const next = new Set(this.state.selection);\n for (let i = start; i <= end; i++) next.add(assets[i].uuid);\n this.set({ selection: next });\n }\n\n /** Replace the whole selection in one update (e.g. \"select all\", marquee drag). */\n setSelection(uuids: Iterable<string>): void {\n const next = new Set(uuids);\n // Re-anchor to the last item so a following shift-click extends from the end of\n // this selection rather than a stale pivot from before it (null when empty).\n let last: string | null = null;\n for (const u of next) last = u;\n this.selectionAnchor = last;\n this.set({ selection: next });\n }\n\n clearSelection(): void {\n this.selectionAnchor = null;\n this.set({ selection: new Set() });\n }\n\n // ── mutations (light-dam's management layer) ───────────────────────────────\n // Orchestrated in core so the CRUD lives with dam-core, and every mutation\n // reloads the current scope. Endpoints verified against js-admin-react-filerobot-v5.\n\n /** Path of the folder currently in view (root when not in folder scope). */\n get currentFolderPath(): string {\n return this.state.scope.type === 'folder' ? (this.state.scope.value ?? '/') : '/';\n }\n\n /**\n * Folder mutations change the hierarchy — re-fetch the relevant tree nodes\n * (root, current, expanded) and swap them in atomically. Unlike a blank-then-\n * refill, this never shows an empty tree, keeps unrelated cached nodes, and a\n * `treeSeq` guard discards a superseded refresh.\n */\n private async refreshTree(): Promise<void> {\n const seq = ++this.treeSeq;\n const paths = new Set(['/', this.currentFolderPath, ...this.state.expandedFolders]);\n const updated: Record<string, Folder[]> = {};\n await Promise.all(\n [...paths].map(async (p) => {\n try {\n updated[p] = await getFolders(this.client, { folderPath: p });\n } catch {\n /* leave this node as-is */\n }\n }),\n );\n if (seq !== this.treeSeq) return;\n this.set({ treeChildren: { ...this.state.treeChildren, ...updated } });\n }\n\n private static parentOf(path: string): string {\n return path.replace(/\\/[^/]*$/, '') || '/';\n }\n\n /** Public: resolve a cached folder's path by uuid (e.g. for the move picker). */\n findFolderPath(uuid: string): string | undefined {\n return this.folderPath(uuid);\n }\n\n /** Find a cached folder's path by uuid (across the tree cache + current list). */\n private folderPath(uuid: string): string | undefined {\n for (const list of [this.state.folders, ...Object.values(this.state.treeChildren)]) {\n const found = list.find((f) => f.uuid === uuid);\n if (found) return found.path;\n }\n return undefined;\n }\n\n /**\n * After a folder is renamed/moved/deleted, keep the user out of a \"ghost\"\n * scope: if they're viewing the affected folder (or a descendant), remap the\n * scope to its new path (or the parent on delete). Returns true if it redirected\n * (which itself triggers a reload), so the caller can skip its own load().\n */\n private redirectScope(oldPath: string, newPath: string | null): boolean {\n const { scope } = this.state;\n if (scope.type !== 'folder' || !scope.value) return false;\n const cur = scope.value;\n if (cur !== oldPath && !cur.startsWith(oldPath + '/')) return false;\n if (newPath === null) {\n this.openFolder(BrowseStore.parentOf(oldPath));\n } else {\n this.openFolder(cur === oldPath ? newPath : newPath + cur.slice(oldPath.length));\n }\n return true;\n }\n\n async createFolder(name: string): Promise<void> {\n await createFolder(this.client, name, this.currentFolderPath);\n await this.refreshTree();\n await this.load();\n }\n\n async renameFolderById(uuid: string, name: string): Promise<void> {\n const oldPath = this.folderPath(uuid);\n await renameFolder(this.client, uuid, name);\n await this.refreshTree();\n const newPath = oldPath ? `${BrowseStore.parentOf(oldPath).replace(/\\/$/, '')}/${name}` : null;\n if (!(oldPath && newPath && this.redirectScope(oldPath, newPath))) await this.load();\n }\n\n async deleteFolderById(uuid: string): Promise<void> {\n const oldPath = this.folderPath(uuid);\n await deleteFolders(this.client, [uuid]);\n await this.refreshTree();\n if (!(oldPath && this.redirectScope(oldPath, null))) await this.load();\n }\n\n async moveFolderById(uuid: string, destination: string): Promise<void> {\n const oldPath = this.folderPath(uuid);\n await moveFolders(this.client, [uuid], destination);\n await this.refreshTree();\n const leaf = oldPath?.split('/').pop() ?? '';\n const newPath = oldPath ? `${destination.replace(/\\/$/, '')}/${leaf}` : null;\n if (!(oldPath && newPath && this.redirectScope(oldPath, newPath))) await this.load();\n }\n\n async renameAsset(uuid: string, name: string): Promise<void> {\n await renameFile(this.client, uuid, name);\n await this.load();\n }\n\n async deleteAssets(uuids: string[]): Promise<void> {\n await deleteFiles(this.client, uuids); // bulk endpoint — one request\n this.clearSelection();\n await this.load();\n }\n\n async moveAssets(uuids: string[], destination: string): Promise<void> {\n await moveFiles(this.client, uuids, destination); // bulk endpoint — one request\n this.clearSelection();\n await this.load();\n }\n\n // ── data loading ───────────────────────────────────────────────────────────\n\n /** Translate the active filter facets into the api query shape (undefined = off). */\n private facetParams(): FacetFilters {\n const { types, size, duration, created, updated } = this.state.filters;\n return {\n types: types.length ? types : undefined,\n sizeBucket: size ?? undefined,\n durationRange: duration ?? undefined,\n createdWithin: created ?? undefined,\n updatedWithin: updated ?? undefined,\n };\n }\n\n /**\n * Fetch one page of the current scope's assets starting at server `offset`.\n *\n * Folder/label/all are server-paged (offset + PAGE_SIZE) so infinite scroll\n * appends page by page. Favourites is a known uuid set, so it's fetched\n * directly by uuid (cheap, bounded by MAX_FAVOURITES) and filtered/sorted in\n * memory — no whole-library scan — and reports no further pages.\n *\n * `fetched` is the raw count returned by the server (before client dedupe), so\n * the caller advances its offset cursor correctly even when dedupe drops items.\n */\n private async fetchAssetsPage(offset: number): Promise<{\n assets: Asset[];\n total: number;\n truncated: boolean;\n hasMore: boolean;\n fetched: number;\n }> {\n const { scope, sortBy, sortDirection } = this.state;\n const search = this.state.searchQuery || undefined;\n const facets = this.facetParams();\n\n if (scope.type === 'favourites') {\n const wanted = [...this.state.favourites];\n const assets = await getFilesByUuids(this.client, wanted.slice(0, MAX_FAVOURITES));\n const now = Date.now();\n const matched = assets.filter((a) => assetMatchesFacets(a, facets, now));\n const sorted = sortAssets(matched, sortBy, sortDirection);\n return {\n assets: sorted,\n total: sorted.length,\n truncated: wanted.length > MAX_FAVOURITES,\n hasMore: false,\n fetched: 0,\n };\n }\n\n const base = { sortBy, sortDirection, search, ...facets, offset, limit: PAGE_SIZE };\n let page;\n if (scope.type === 'folder') {\n // With an active search, recurse into subfolders so matches deeper in the\n // tree surface too; otherwise list only the folder's direct children.\n const recursive = this.state.searchQuery.trim() ? 1 : 0;\n page = await getFilesPage(this.client, { ...base, folder: scope.value ?? '/', recursive });\n } else if (scope.type === 'label') {\n const label = this.state.labels.find((l) => l.uuid === scope.value);\n const q = label ? labelFilterQuery(label) : undefined;\n page = await getFilesPage(this.client, { ...base, recursive: 1, q });\n } else {\n page = await getFilesPage(this.client, { ...base, recursive: 1 });\n }\n // A full page means there may be more — the classic infinite-scroll signal,\n // robust even when the API omits a grand `total` (a short/empty next page\n // then stops it). `total` is kept only for the \"all N loaded\" tally.\n const hasMore = page.assets.length === PAGE_SIZE;\n return {\n assets: page.assets,\n total: page.total,\n truncated: false,\n hasMore,\n fetched: page.assets.length,\n };\n }\n\n /** Reload everything for the current scope from page 1. Stale responses are discarded. */\n async load(): Promise<void> {\n const seq = ++this.loadSeq;\n this.set({ loading: true, loadingMore: false, error: null });\n try {\n // The Folders sidebar stays usable in every scope: always list the children\n // of the current folder path (root for non-folder scopes), in parallel with\n // the scope's assets — so a label/favourites view never shows stale folders.\n const foldersPromise = getFolders(this.client, { folderPath: this.currentFolderPath });\n const pagePromise = this.fetchAssetsPage(0);\n\n const [folders, page] = await Promise.all([foldersPromise, pagePromise]);\n\n if (seq !== this.loadSeq) return; // a newer load superseded this one\n this.fetchedCount = page.fetched; // reset the infinite-scroll offset cursor\n this.set({\n folders,\n assets: page.assets,\n total: page.total,\n truncated: page.truncated,\n hasMore: page.hasMore,\n loading: false,\n // keep the tree cache warm for the folder we just listed\n treeChildren: { ...this.state.treeChildren, [this.currentFolderPath]: folders },\n });\n } catch (err) {\n if (seq !== this.loadSeq) return;\n this.set({ error: err instanceof Error ? err.message : String(err), loading: false });\n }\n }\n\n /**\n * Append the next page of assets for the current scope (infinite scroll).\n * No-op while already loading, when no more pages exist, or if the scope\n * changes mid-flight (the `loadSeq` guard discards a stale page).\n *\n * Offset comes from `fetchedCount` (raw items fetched), NOT the displayed\n * length, so dedupe dropping an item never re-requests the same page. Appended\n * assets are still de-duplicated by uuid so a shifting window can't double a card.\n */\n async loadMore(): Promise<void> {\n if (this.state.loading || this.state.loadingMore || !this.state.hasMore) return;\n const seq = this.loadSeq; // tie to the current load; a new load() bumps it and we bail\n this.set({ loadingMore: true });\n try {\n const page = await this.fetchAssetsPage(this.fetchedCount);\n if (seq !== this.loadSeq) return; // scope/filters changed during the fetch\n this.fetchedCount += page.fetched;\n const seen = new Set(this.state.assets.map((a) => a.uuid));\n const fresh = page.assets.filter((a) => !seen.has(a.uuid));\n this.set({\n assets: [...this.state.assets, ...fresh],\n total: page.total,\n hasMore: page.hasMore,\n loadingMore: false,\n error: null, // a recovered page clears any prior load-more failure\n });\n } catch (err) {\n if (seq !== this.loadSeq) return;\n // A failed page shouldn't wipe what's already shown — just stop and surface it.\n this.set({ error: err instanceof Error ? err.message : String(err), loadingMore: false });\n }\n }\n}\n","// FUTURE @scaleflex/dam-core — default Favourites persistence. See ../README.md.\n// localStorage-backed; the out-of-the-box adapter. Hosts with real user accounts\n// (Portals/Spotlight) inject their own per-user adapter instead.\nimport type { FavouritesAdapter } from './browse-store';\n\nexport class LocalFavouritesAdapter implements FavouritesAdapter {\n constructor(private readonly key = 'sfx-light-dam:favourites') {}\n\n private read(): Set<string> {\n try {\n const raw = localStorage.getItem(this.key);\n return new Set(raw ? (JSON.parse(raw) as string[]) : []);\n } catch {\n return new Set();\n }\n }\n\n private write(ids: Set<string>): void {\n try {\n localStorage.setItem(this.key, JSON.stringify([...ids]));\n } catch {\n /* storage unavailable / quota — favourites are best-effort locally */\n }\n }\n\n list(): string[] {\n return [...this.read()];\n }\n\n has(uuid: string): boolean {\n return this.read().has(uuid);\n }\n\n add(uuid: string): void {\n const ids = this.read();\n ids.add(uuid);\n this.write(ids);\n }\n\n remove(uuid: string): void {\n const ids = this.read();\n ids.delete(uuid);\n this.write(ids);\n }\n}\n","// FUTURE @scaleflex/dam-core — CDN/transform URL helpers. See ../README.md.\n// Mirrors @scaleflex/asset-picker's thumbnail/transform helpers.\n\nexport interface TransformParams {\n width?: number;\n height?: number;\n /** 1–100, or \"auto\". */\n quality?: number | 'auto';\n}\n\n/** Append Filerobot CDN sizing params (w/h/q) to a permalink, preserving existing params. */\nexport function addCdnParams(cdnUrl: string, params: TransformParams): string {\n const url = new URL(cdnUrl);\n if (params.width) url.searchParams.set('w', String(params.width));\n if (params.height) url.searchParams.set('h', String(params.height));\n if (params.quality !== undefined) url.searchParams.set('q', String(params.quality));\n return url.toString();\n}\n\n// ── transformed download / embed (mirrors js-admin-react-filerobot-v5) ─────────\n\n/** Output formats the transform pipeline supports. */\nexport type DownloadFormat = 'webp' | 'jpeg' | 'png' | 'gif';\n\n/** Quality presets → numeric `q` (JPEG/WEBP), matching the reference admin. */\nexport const QUALITY_LEVELS = { excellent: 90, good: 75, fair: 60 } as const;\nexport type QualityLevel = keyof typeof QUALITY_LEVELS;\n\n/** Standardized resize presets (max box, in px); \"Full HD\" + the reference's large/medium/small. */\nexport const RESIZE_PRESETS = { fullhd: 1920, large: 1200, medium: 600, small: 300 } as const;\nexport type ResizePreset = keyof typeof RESIZE_PRESETS;\n\n/** Animated formats need the dual cloudimage token so the still + animated paths both work. */\nconst FORMAT_TO_CLOUDIMG: Partial<Record<DownloadFormat, string>> = {\n webp: 'awebp,webp',\n gif: 'agif,gif',\n};\n\nexport interface TransformOptions {\n format?: DownloadFormat;\n /** Numeric q (1–100). Use QUALITY_LEVELS for the named presets. Omit for \"Original\" quality. */\n quality?: number;\n width?: number;\n height?: number;\n /**\n * Aspect-ratio handling when width/height are set (the embed/options \"lock\" toggle).\n * `true` → `func=bound` (fit inside the box, keep ratio); `false` → `func=cover`\n * (fill the box, may crop). Omit to leave `func` off (the reference admin's default\n * resize). Mirrors Portals' strict-size switch.\n */\n strictSizes?: boolean;\n}\n\nfunction applyTransform(url: URL, t: TransformOptions): void {\n // `download=1` on the source permalink bypasses transformations and returns the\n // original — drop it so format/quality/resize are actually honoured.\n url.searchParams.delete('download');\n if (t.width) url.searchParams.set('w', String(t.width));\n if (t.height) url.searchParams.set('h', String(t.height));\n if (t.quality != null) url.searchParams.set('q', String(t.quality));\n if (t.format) url.searchParams.set('force_format', FORMAT_TO_CLOUDIMG[t.format] ?? t.format);\n // Only meaningful alongside an explicit size; `bound` keeps ratio, `cover` fills/crops.\n if (t.strictSizes !== undefined && (t.width || t.height)) {\n url.searchParams.set('func', t.strictSizes ? 'bound' : 'cover');\n }\n}\n\n/** The downloaded file's name with the new extension (jpeg → jpg) when reformatting. */\nexport function transformedFileName(originalName: string, format?: DownloadFormat): string {\n if (!format) return originalName;\n const ext = format === 'jpeg' ? 'jpg' : format;\n // Strip the trailing extension, but never reduce to an empty base (\".htaccess\").\n const base = originalName.replace(/\\.[^./\\\\]+$/, '') || originalName;\n return `${base}.${ext}`;\n}\n\n/**\n * Build a CDN URL that serves the *transformed* asset as a download attachment.\n * Uses `force_download=<name>` (not `download=1`, which bypasses transforms and\n * returns the original) — same contract as the reference admin's getFileWithNewParams.\n */\nexport function buildTransformedDownloadUrl(\n cdnUrl: string,\n originalName: string,\n t: TransformOptions = {},\n): string {\n const url = new URL(cdnUrl);\n applyTransform(url, t);\n url.searchParams.set('force_download', transformedFileName(originalName, t.format));\n return url.toString();\n}\n\n/** Build a CDN URL for the transformed asset for embedding/linking (no download flag). */\nexport function buildEmbedUrl(cdnUrl: string, t: TransformOptions = {}): string {\n const url = new URL(cdnUrl);\n applyTransform(url, t);\n return url.toString();\n}\n","// FUTURE @scaleflex/dam-core — thumbnail + file-type icon helpers. See ../README.md.\n// Ported from @scaleflex/asset-picker (utils/thumbnail.ts, utils/file-type.ts):\n// CDN-generated thumbnails (video poster, PDF first page, sized images) on the\n// transform-capable `assets.filerobot.com` host, with hosted file-type icon\n// fallbacks for non-thumbnailable assets.\nimport type { Asset } from '../types/asset.types';\n\nconst FR_CDN = /^(https:\\/\\/)([a-z0-9_-]+)\\.filerobot\\.com\\/(?:v\\d+\\/)?/i;\n\n/**\n * Rewrite a CDN URL to the transform-capable `assets.filerobot.com/{token}/...`\n * host and strip any `/vN/` version prefix, so CDN image-processing params apply.\n */\nexport function getFormattedPreviewUrl(cdnUrl: string): string {\n if (!cdnUrl) return '';\n const stripVersion = (u: string) => u.replace(/(\\.filerobot\\.com\\/[a-z0-9_-]+)\\/v\\d+\\//i, '$1/');\n if (cdnUrl.includes('assets.filerobot.com')) return stripVersion(cdnUrl);\n const m = cdnUrl.match(FR_CDN);\n if (m) return stripVersion(cdnUrl.replace(FR_CDN, `$1assets.filerobot.com/${m[2]}/`));\n return cdnUrl;\n}\n\nfunction withParams(url: string, params: Record<string, string>): string {\n try {\n const u = new URL(url);\n for (const [k, v] of Object.entries(params)) u.searchParams.set(k, v);\n return u.toString();\n } catch {\n return url;\n }\n}\n\n/**\n * Best thumbnail URL for an asset, by type:\n * - video → API poster (`info.video_thumbnail`…) sized via CDN\n * - pdf → first page rendered as image (`doc_page=1`)\n * - image / has-API-thumbnail → sized CDN image\n * - audio / 3D / other → \"\" (caller shows a file-type icon)\n */\nexport function getThumbnailUrl(asset: Asset, size = 240): string {\n const w = String(size);\n\n if (asset.type === 'video') {\n if (!asset.thumbnailUrl) return '';\n return withParams(getFormattedPreviewUrl(asset.thumbnailUrl), { w, force_format: 'webp,jpeg' });\n }\n\n if (asset.type === 'document' && /\\.pdf$/i.test(asset.name)) {\n const cdn = getFormattedPreviewUrl(asset.cdnUrl ?? '');\n if (!cdn) return '';\n const stripped = cdn.replace(/([?&])func=proxy&?/, '$1').replace(/[?&]$/, '');\n return withParams(stripped, {\n w,\n force_format: 'webp,jpeg',\n doc_page: '1',\n bypass_process_proxy: '1',\n });\n }\n\n if (asset.type === 'image' || asset.thumbnailUrl) {\n const raw = asset.thumbnailUrl ?? asset.cdnUrl;\n if (!raw) return '';\n return withParams(getFormattedPreviewUrl(raw), { w, func: 'bound' });\n }\n\n return ''; // audio, model, generic documents → icon fallback\n}\n\n// ── file-type icons (hosted v3 SVG set, ported verbatim from asset-picker) ────\n\nconst FILE_TYPES_ICON_BASE = 'https://scaleflex.cloudimg.io/v7/assets/file-types/v3/';\n\n// asset-picker's set has no 3D icon — local override (inline isometric-cube SVG,\n// no remote dependency) for glb/gltf/obj/… so 3D assets aren't shown as GENERIC.\nconst MODEL_ICON_SVG =\n `<svg xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 48 48\" fill=\"none\">` +\n `<path d=\"M24 4 42 14v20L24 44 6 34V14z\" fill=\"#eef2ff\" stroke=\"#6366f1\" stroke-width=\"2\" stroke-linejoin=\"round\"/>` +\n `<path d=\"M24 4 42 14 24 24 6 14z\" fill=\"#c7d2fe\" stroke=\"#6366f1\" stroke-width=\"2\" stroke-linejoin=\"round\"/>` +\n `<path d=\"M24 24v20M24 24 6 14M24 24 42 14\" stroke=\"#6366f1\" stroke-width=\"2\" stroke-linejoin=\"round\"/>` +\n `</svg>`;\nconst MODEL_ICON = `data:image/svg+xml,${encodeURIComponent(MODEL_ICON_SVG)}`;\n\nconst FILE_TYPES_AND_ICONS: Record<string, string> = {\n _default: FILE_TYPES_ICON_BASE + 'GENERIC.svg?vh=9a518a',\n // Images\n png: FILE_TYPES_ICON_BASE + 'PNG.svg?vh=96cd9a',\n jpg: FILE_TYPES_ICON_BASE + 'JPG.svg?vh=06e819',\n jpg2: FILE_TYPES_ICON_BASE + 'JPG2.svg?vh=f0eb7f',\n jpeg: FILE_TYPES_ICON_BASE + 'JPEG.svg?vh=6a65e9',\n gif: FILE_TYPES_ICON_BASE + 'GIF.svg?vh=c3c2c3',\n bmp: FILE_TYPES_ICON_BASE + 'BMP.svg?vh=d2243a',\n webp: FILE_TYPES_ICON_BASE + 'WEBP.svg?vh=fedd74',\n svg: FILE_TYPES_ICON_BASE + 'SVG.svg?vh=a15e46',\n tiff: FILE_TYPES_ICON_BASE + 'TIFF.svg?vh=1f30c3',\n tif: FILE_TYPES_ICON_BASE + 'TIF.svg?vh=b383c9',\n heic: FILE_TYPES_ICON_BASE + 'HEIC.svg?vh=84adfe',\n avif: FILE_TYPES_ICON_BASE + 'AVIF.svg?vh=536b30',\n ico: FILE_TYPES_ICON_BASE + 'ICO.svg?vh=79063d',\n psd: FILE_TYPES_ICON_BASE + 'PSD.svg?vh=be6140',\n psb: FILE_TYPES_ICON_BASE + 'PSB.svg?vh=678646',\n ai: FILE_TYPES_ICON_BASE + 'AI.svg?vh=84b254',\n dwg: FILE_TYPES_ICON_BASE + 'DWG.svg?vh=971fb3',\n // Video\n mp4: FILE_TYPES_ICON_BASE + 'MP4.svg?vh=42f175',\n webm: FILE_TYPES_ICON_BASE + 'WEBM.svg?vh=26a84a',\n avi: FILE_TYPES_ICON_BASE + 'AVI.svg?vh=d22ba8',\n mpeg: FILE_TYPES_ICON_BASE + 'MPEG.svg?vh=ba93bb',\n ogv: FILE_TYPES_ICON_BASE + 'OGV.svg?vh=74d453',\n '3gp': FILE_TYPES_ICON_BASE + '3GP.svg?vh=f0d388',\n '3g2': FILE_TYPES_ICON_BASE + '3G2.svg?vh=04c652',\n swf: FILE_TYPES_ICON_BASE + 'SWF.svg?vh=3955e2',\n fla: FILE_TYPES_ICON_BASE + 'FLA.svg?vh=daf585',\n m3u8: FILE_TYPES_ICON_BASE + 'M3U8.svg?vh=7d5e62',\n // Audio\n mp3: FILE_TYPES_ICON_BASE + 'MP3.svg?vh=66bbef',\n wav: FILE_TYPES_ICON_BASE + 'WAV.svg?vh=d7a7d5',\n aac: FILE_TYPES_ICON_BASE + 'AAC.svg?vh=07f3f9',\n oga: FILE_TYPES_ICON_BASE + 'OGA.svg?vh=a5c622',\n opus: FILE_TYPES_ICON_BASE + 'OPUS.svg?vh=9548b1',\n weba: FILE_TYPES_ICON_BASE + 'WEBA.svg?vh=4dcf70',\n mid: FILE_TYPES_ICON_BASE + 'MID.svg?vh=3f0e29',\n midi: FILE_TYPES_ICON_BASE + 'MIDI.svg?vh=9fedec',\n cda: FILE_TYPES_ICON_BASE + 'CDA.svg?vh=85b83b',\n // Documents\n pdf: FILE_TYPES_ICON_BASE + 'PDF.svg?vh=18c5f7',\n doc: FILE_TYPES_ICON_BASE + 'DOC.svg?vh=d1b47c',\n docx: FILE_TYPES_ICON_BASE + 'DOCX.svg?vh=1eb6b0',\n txt: FILE_TYPES_ICON_BASE + 'TXT.svg?vh=307979',\n rtf: FILE_TYPES_ICON_BASE + 'RTF.svg?vh=978c5f',\n xls: FILE_TYPES_ICON_BASE + 'XLS.svg?vh=13b5f7',\n xlsx: FILE_TYPES_ICON_BASE + 'XLSX.svg?vh=79d64a',\n ppt: FILE_TYPES_ICON_BASE + 'PPT.svg?vh=4ee29b',\n pptx: FILE_TYPES_ICON_BASE + 'PPTX.svg?vh=8b1568',\n csv: FILE_TYPES_ICON_BASE + 'CSV.svg?vh=4add78',\n odt: FILE_TYPES_ICON_BASE + 'ODT.svg?vh=940781',\n ods: FILE_TYPES_ICON_BASE + 'ODS.svg?vh=9fbe9a',\n odp: FILE_TYPES_ICON_BASE + 'ODP.svg?vh=bf892d',\n dbf: FILE_TYPES_ICON_BASE + 'DBF.svg?vh=457bd4',\n vsd: FILE_TYPES_ICON_BASE + 'VSD.svg?vh=8a9ccb',\n abw: FILE_TYPES_ICON_BASE + 'ABW.svg?vh=313dc7',\n epub: FILE_TYPES_ICON_BASE + 'EPUB.svg?vh=15263d',\n azw: FILE_TYPES_ICON_BASE + 'AZW.svg?vh=a018b1',\n ics: FILE_TYPES_ICON_BASE + 'ICS.svg?vh=909f63',\n ogx: FILE_TYPES_ICON_BASE + 'OGX.svg?vh=f694d2',\n // Archives\n zip: FILE_TYPES_ICON_BASE + 'ZIP.svg?vh=84f98b',\n rar: FILE_TYPES_ICON_BASE + 'RAR.svg?vh=1d6423',\n '7z': FILE_TYPES_ICON_BASE + '7Z.svg?vh=e007e5',\n tar: FILE_TYPES_ICON_BASE + 'TAR.svg?vh=603aed',\n gz: FILE_TYPES_ICON_BASE + 'GZ.svg?vh=de13f7',\n bz: FILE_TYPES_ICON_BASE + 'BZ.svg?vh=0374ff',\n bz2: FILE_TYPES_ICON_BASE + 'BZ2.svg?vh=e14294',\n arc: FILE_TYPES_ICON_BASE + 'ARC.svg?vh=942fad',\n jar: FILE_TYPES_ICON_BASE + 'JAR.svg?vh=149796',\n mpkg: FILE_TYPES_ICON_BASE + 'MPKG.svg?vh=dea655',\n // Fonts\n ttf: FILE_TYPES_ICON_BASE + 'TTF.svg?vh=d2e2c1',\n otf: FILE_TYPES_ICON_BASE + 'OTF.svg?vh=c904fd',\n woff: FILE_TYPES_ICON_BASE + 'WOFF.svg?vh=4b8177',\n woff2: FILE_TYPES_ICON_BASE + 'WOFF2.svg?vh=b532d3',\n eot: FILE_TYPES_ICON_BASE + 'EOT.svg?vh=a54980',\n // Code / Scripts\n js: FILE_TYPES_ICON_BASE + 'JS.svg?vh=524691',\n mjs: FILE_TYPES_ICON_BASE + 'MJS.svg?vh=d57921',\n ts: FILE_TYPES_ICON_BASE + 'TS.svg?vh=9af3ae',\n css: FILE_TYPES_ICON_BASE + 'CSS.svg?vh=287863',\n html: FILE_TYPES_ICON_BASE + 'HTML.svg?vh=fa7a87',\n htm: FILE_TYPES_ICON_BASE + 'HTM.svg?vh=21323d',\n xhtml: FILE_TYPES_ICON_BASE + 'XHTML.svg?vh=e6d6a9',\n xul: FILE_TYPES_ICON_BASE + 'XUL.svg?vh=6c9c71',\n json: FILE_TYPES_ICON_BASE + 'JSON.svg?vh=104c9e',\n jsonld: FILE_TYPES_ICON_BASE + 'JSONLD.svg?vh=f30c0f',\n xml: FILE_TYPES_ICON_BASE + 'XML.svg?vh=7f7194',\n php: FILE_TYPES_ICON_BASE + 'PHP.svg?vh=503e36',\n sh: FILE_TYPES_ICON_BASE + 'SH.svg?vh=3b820e',\n csh: FILE_TYPES_ICON_BASE + 'CSH.svg?vh=08c0cc',\n // Executables / Disk images\n exe: FILE_TYPES_ICON_BASE + 'EXE.svg?vh=ccca53',\n iso: FILE_TYPES_ICON_BASE + 'ISO.svg?vh=064b8f',\n bin: FILE_TYPES_ICON_BASE + 'BIN.svg?vh=1e9618',\n // 3D models (local override — not in asset-picker's hosted set)\n glb: MODEL_ICON,\n gltf: MODEL_ICON,\n obj: MODEL_ICON,\n fbx: MODEL_ICON,\n stl: MODEL_ICON,\n usdz: MODEL_ICON,\n ply: MODEL_ICON,\n '3ds': MODEL_ICON,\n dae: MODEL_ICON,\n};\n\n/** Generic fallback icon (last resort for the <img> error cascade). */\nexport function getDefaultFileTypeIconUrl(): string {\n return FILE_TYPES_AND_ICONS._default;\n}\n\n/** File-type icon URL for a file name/extension; GENERIC when unmapped. */\nexport function getFileTypeIconUrl(name: string): string {\n const ext = (name.split('.').pop() || '').toLowerCase();\n return FILE_TYPES_AND_ICONS[ext] || FILE_TYPES_AND_ICONS._default;\n}\n","// Presentation formatters for asset metadata (size / date / dimensions). Pure,\n// dependency-free, and locale-aware — shared so every DAM widget renders the\n// same \"File info\" values. UI-free (returns plain strings); callers do the markup.\n\n/** Human-readable byte size (\"—\" when unknown, e.g. \"1.4 MB\", \"512 B\"). */\nexport function formatFileSize(bytes?: number | null): string {\n // Guard non-finite/NaN/negative too — `raw.size` flows in un-coerced from the API.\n if (bytes == null || !Number.isFinite(bytes) || bytes < 0) return '—';\n const units = ['B', 'KB', 'MB', 'GB', 'TB'];\n let value = bytes;\n let i = 0;\n while (value >= 1024 && i < units.length - 1) {\n value /= 1024;\n i++;\n }\n // Whole bytes; larger units show 1 decimal under 10, none at/above. Precision is\n // decided on the value rounded to 1dp so 9.95 prints \"10 KB\" (not \"10.0 KB\").\n const fmt = (v: number, unit: number) => {\n const dp = unit > 0 && Math.round(v * 10) / 10 < 10 ? 1 : 0;\n const str = v.toFixed(dp);\n return { str, rounded: Number(str) };\n };\n const first = fmt(value, i);\n let str = first.str;\n // A value that rounds up to 1024 rolls into the next unit (1024 KB → \"1.0 MB\").\n if (first.rounded >= 1024 && i < units.length - 1) {\n value /= 1024;\n i++;\n str = fmt(value, i).str;\n }\n return `${str} ${units[i]}`;\n}\n\n/** Short, locale-aware date (\"—\" when missing; echoes the raw value if unparseable). */\nexport function formatDate(iso?: string | null): string {\n if (!iso) return '—';\n const d = new Date(iso);\n if (Number.isNaN(d.getTime())) return iso;\n return d.toLocaleDateString(undefined, { year: 'numeric', month: 'short', day: 'numeric' });\n}\n\n/** \"W × H\" pixel dimensions, or \"—\" when either side is missing. */\nexport function formatDimensions(width?: number | null, height?: number | null): string {\n return width && height ? `${width} × ${height}` : '—';\n}\n","// Debounce a void-returning function: collapses bursts of calls into one,\n// firing `delay` ms after the last call. The returned function carries a\n// `.cancel()` to drop a pending trailing call (e.g. on disconnect). Shared so\n// every DAM widget debounces search/scroll/resize the same way.\n\n// eslint-disable-next-line @typescript-eslint/no-explicit-any\nexport function debounce<T extends (...args: any[]) => void>(\n fn: T,\n delay: number,\n): T & { cancel(): void } {\n let timer: ReturnType<typeof setTimeout>;\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n const debounced = ((...args: any[]) => {\n clearTimeout(timer);\n timer = setTimeout(() => fn(...args), delay);\n }) as T & { cancel(): void };\n debounced.cancel = () => clearTimeout(timer);\n return debounced;\n}\n"],"names":["DEFAULT_API_HOST","EXCHANGE_TIMEOUT_MS","DEFAULT_EXCHANGE_RETRIES","DEFAULT_EXCHANGE_RETRY_DELAY_MS","isTransientStatus","status","sleep","ms","resolve","getApiBase","container","apiDomain","exchangeSassKey","securityTemplateId","opts","url","retries","retryDelayMs","lastError","attempt","controller","timeoutId","response","data","err","buildAuthHeaders","auth","resolvedSassKey","headers","isAssetPickerAuth","normalizeAuth","resolveAuth","cfg","apiBase","sassKey","TIMEOUT_MS","DamClient","resolved","path","params","key","value","body","method","init","retried","_a","errorMsg","text","normalizeFolder","raw","parentPath","getFolders","client","sortField","sortDir","createFolder","leafName","parentFolderPath","cleanParent","fullPath","res","renameFolder","uuid","newName","deleteFolders","uuids","moveFolders","destinationPath","SIZE_BUCKETS","DATE_PRESET_DAYS","sinceMidnight","preset","nowMs","d","DEFAULT_API_FIELDS","EXT","addExt","type","exts","e","toAssetType","ext","t","normalizeFile","_b","_c","_d","_e","_f","_g","buildFilesQuery","fields","parts","min","max","now","getFilesPage","query","assets","total","getFiles","TYPE_TO_FILTER","assetMatchesFacets","asset","f","sortAssets","sortBy","dir","mult","a","b","ka","kb","getFilesByUuids","out","next","worker","i","lanes","renameFile","deleteFiles","moveFiles","destinationFolder","getLabels","labelFilterQuery","label","presetUrl","originalUrl","presetName","normalizeVariation","originalVariation","presetVariation","base","assembleVariations","variants","presets","presetNames","p","custom","v","bgRemoved","presetRows","getFileVariations","normalizePresets","getPresets","loadVariations","createArchive","target","getArchiveStatus","taskId","IN_PROGRESS","archiveStatusOutcome","progress","pollArchive","interval","timeout","maxRetries","r","deadline","done","archiveDownloadUrl","sep","createAndPollArchive","SORT_FIELDS","field","apiParams","getFilesRaw","getFilesStats","getFoldersRaw","getFolderPreviews","folderUuids","PAGE_SIZE","MAX_FAVOURITES","EMPTY_FILTERS","BrowseStore","children","expanded","isFav","optimistic","reverted","listener","patch","l","scope","viewMode","gridSize","sortDirection","searchQuery","types","size","duration","created","updated","multi","willSelect","toIdx","anchorIdx","start","end","last","u","seq","paths","list","found","oldPath","newPath","cur","name","destination","leaf","offset","search","facets","wanted","matched","sorted","page","recursive","q","hasMore","foldersPromise","pagePromise","folders","seen","fresh","LocalFavouritesAdapter","ids","addCdnParams","cdnUrl","QUALITY_LEVELS","RESIZE_PRESETS","FORMAT_TO_CLOUDIMG","applyTransform","transformedFileName","originalName","format","buildTransformedDownloadUrl","buildEmbedUrl","FR_CDN","getFormattedPreviewUrl","stripVersion","m","withParams","k","getThumbnailUrl","w","cdn","stripped","FILE_TYPES_ICON_BASE","MODEL_ICON_SVG","MODEL_ICON","FILE_TYPES_AND_ICONS","getDefaultFileTypeIconUrl","getFileTypeIconUrl","formatFileSize","bytes","units","fmt","unit","dp","str","first","formatDate","iso","formatDimensions","width","height","debounce","fn","delay","timer","debounced","args"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"index.cjs","sources":["../src/auth/auth.service.ts","../src/api/dam-client.ts","../src/api/folders.service.ts","../src/api/files.service.ts","../src/api/labels.service.ts","../src/helpers/variations.ts","../src/api/variations.service.ts","../src/api/archive.service.ts","../src/api/filerobot-browse.service.ts","../src/store/browse-store.ts","../src/store/local-favourites.ts","../src/helpers/cdn.ts","../src/helpers/thumbnail.ts","../src/helpers/format.ts","../src/helpers/debounce.ts"],"sourcesContent":["// @scaleflex/dam-core — canonical auth service.\n//\n// The single source of truth for Filerobot auth across the widgets. Accepts the canonical\n// (`container`) shape AND asset-picker's drifted (`projectToken`) shape, normalizing the\n// latter. See plan/05 + plan/11 (F1).\nimport type {\n AnyAuthConfig,\n AssetPickerAuth,\n AuthConfig,\n AuthHeaders,\n ResolvedAuth,\n} from './auth.types';\n\ninterface SassKeyResponse {\n status: 'success' | 'error';\n key: string;\n msg?: string;\n}\n\nconst DEFAULT_API_HOST = 'https://api.filerobot.com';\n\nconst EXCHANGE_TIMEOUT_MS = 30_000;\nconst DEFAULT_EXCHANGE_RETRIES = 2;\nconst DEFAULT_EXCHANGE_RETRY_DELAY_MS = 400;\n\n/**\n * Whether an exchange HTTP status is worth retrying. The Filerobot key endpoint\n * can return a transient **404** (as well as 408/429/5xx) for a perfectly valid\n * security template — e.g. an edge/cache miss — so a valid config intermittently\n * fails the exchange. Retrying these a few times absorbs the blip.\n */\nfunction isTransientStatus(status: number): boolean {\n return status === 404 || status === 408 || status === 429 || status >= 500;\n}\n\nconst sleep = (ms: number) => new Promise<void>((resolve) => setTimeout(resolve, ms));\n\n/**\n * Container base URL, e.g. \"https://api.filerobot.com/akli\".\n * `apiDomain` overrides the default host. The REST client appends `/v5` to this\n * for the Files/Folders API.\n */\nexport function getApiBase(container: string, apiDomain?: string): string {\n const host = (apiDomain || DEFAULT_API_HOST).replace(/\\/+$/, '');\n return `${host}/${container}`;\n}\n\n/**\n * Exchange a security template id for a short-lived SASS key.\n *\n * Resilience: the response is fetched with `cache: 'no-store'` (the key is\n * short-lived and security-sensitive — never serve a cached key or a cached\n * transient 404), and transient failures (404/408/429/5xx, network errors) are\n * retried a few times with linear backoff. This fixes the intermittent\n * \"SASS key exchange failed (HTTP 404)\" seen with a valid container + template.\n */\nexport async function exchangeSassKey(\n container: string,\n securityTemplateId: string,\n apiDomain?: string,\n opts: { retries?: number; retryDelayMs?: number } = {},\n): Promise<string> {\n const url = `${getApiBase(container, apiDomain)}/key/${encodeURIComponent(securityTemplateId)}`;\n const retries = opts.retries ?? DEFAULT_EXCHANGE_RETRIES;\n const retryDelayMs = opts.retryDelayMs ?? DEFAULT_EXCHANGE_RETRY_DELAY_MS;\n\n let lastError: Error = new Error('SASS key exchange failed');\n\n for (let attempt = 0; attempt <= retries; attempt++) {\n if (attempt > 0) await sleep(retryDelayMs * attempt);\n\n const controller = new AbortController();\n const timeoutId = setTimeout(() => controller.abort(), EXCHANGE_TIMEOUT_MS);\n\n try {\n const response = await fetch(url, { signal: controller.signal, cache: 'no-store' });\n clearTimeout(timeoutId);\n\n if (!response.ok) {\n lastError = new Error(`SASS key exchange failed (HTTP ${response.status})`);\n if (isTransientStatus(response.status) && attempt < retries) continue;\n throw lastError;\n }\n\n const data: SassKeyResponse = await response.json();\n if (data.status === 'error') {\n // A definitive backend rejection (bad/expired template) — don't retry.\n throw new Error(`SASS key exchange failed: ${data.msg || 'Unknown error'}`);\n }\n return data.key;\n } catch (err) {\n clearTimeout(timeoutId);\n if (err instanceof DOMException && err.name === 'AbortError') {\n throw new Error('SASS key exchange timed out');\n }\n // Network-level failures (fetch rejects with a TypeError) are transient.\n if (err instanceof TypeError && attempt < retries) {\n lastError = err;\n continue;\n }\n throw err;\n }\n }\n\n throw lastError;\n}\n\n/** Build auth headers from a (possibly resolved) SASS key. */\nexport function buildAuthHeaders(auth: AuthConfig, resolvedSassKey?: string): AuthHeaders {\n const headers: AuthHeaders = {};\n\n switch (auth.mode) {\n case 'security-template':\n if (!resolvedSassKey) {\n throw new Error(\n '[dam-core] Cannot build auth headers for security-template mode: SASS key ' +\n 'exchange has not been performed. Call resolveAuth() first, or use sass-key mode.',\n );\n }\n headers['X-Filerobot-Key'] = resolvedSassKey;\n break;\n case 'sass-key':\n headers['X-Filerobot-Key'] = auth.sassKey;\n break;\n }\n\n if (auth.airboxPuid) {\n headers['X-Filerobot-Airbox-Puid'] = auth.airboxPuid;\n }\n return headers;\n}\n\n/** Narrow an unknown auth config to the asset-picker (drifted) shape. */\nfunction isAssetPickerAuth(auth: AnyAuthConfig): auth is AssetPickerAuth {\n return auth.mode === 'securityTemplate' || auth.mode === 'sassKey';\n}\n\n/**\n * Normalize any accepted auth shape onto the canonical {@link AuthConfig}. The canonical\n * shape passes through untouched; asset-picker's `projectToken`/camelCase shape is mapped.\n */\nexport function normalizeAuth(auth: AnyAuthConfig): AuthConfig {\n if (!isAssetPickerAuth(auth)) return auth;\n\n if (auth.mode === 'securityTemplate') {\n return {\n mode: 'security-template',\n container: auth.projectToken,\n securityTemplateId: auth.securityTemplateKey,\n airboxPuid: auth.airboxPuid,\n };\n }\n return {\n mode: 'sass-key',\n container: auth.projectToken,\n sassKey: auth.sassKey,\n airboxPuid: auth.airboxPuid,\n };\n}\n\n/**\n * Resolve any accepted auth shape into a ready-to-use base URL + headers.\n * Accepts the canonical {@link AuthConfig} or asset-picker's {@link AssetPickerAuth}.\n * For security-template mode this performs the SASS key exchange.\n */\nexport async function resolveAuth(auth: AnyAuthConfig, apiDomain?: string): Promise<ResolvedAuth> {\n const cfg = normalizeAuth(auth);\n const apiBase = getApiBase(cfg.container, apiDomain);\n\n if (cfg.mode === 'security-template') {\n const sassKey = await exchangeSassKey(cfg.container, cfg.securityTemplateId, apiDomain);\n return { apiBase, headers: buildAuthHeaders(cfg, sassKey), sassKey };\n }\n return { apiBase, headers: buildAuthHeaders(cfg) };\n}\n","// FUTURE @scaleflex/dam-core — REST client. See ../README.md.\n//\n// Unifies @scaleflex/asset-picker's `ApiClient` (request/post) with the PUT/DELETE\n// verbs light-dam needs for asset/folder mutations. Built on a resolved auth\n// (base URL + headers) so it stays decoupled from how auth was obtained.\nimport { resolveAuth } from '../auth';\nimport type { AuthConfig, AuthHeaders, ResolvedAuth } from '../auth';\n\nconst TIMEOUT_MS = 30_000;\n\ntype Params = Record<string, unknown>;\n\nexport class DamClient {\n /** REST base, e.g. \"https://api.filerobot.com/akli/v5\". */\n private readonly baseUrl: string;\n private headers: AuthHeaders;\n // Kept so a short-lived security-template key can be re-exchanged on 401.\n private readonly auth?: AuthConfig;\n private readonly apiDomain?: string;\n\n constructor(resolved: ResolvedAuth, auth?: AuthConfig, apiDomain?: string) {\n this.baseUrl = `${resolved.apiBase}/v5`;\n this.headers = resolved.headers;\n this.auth = auth;\n this.apiDomain = apiDomain;\n }\n\n /** Resolve an AuthConfig (performing key exchange if needed) and build a client. */\n static async create(auth: AuthConfig, apiDomain?: string): Promise<DamClient> {\n const resolved = await resolveAuth(auth, apiDomain);\n return new DamClient(resolved, auth, apiDomain);\n }\n\n get<T>(path: string, params?: Params): Promise<T> {\n const url = new URL(`${this.baseUrl}${path}`);\n if (params) {\n for (const [key, value] of Object.entries(params)) {\n if (value === undefined || value === null) continue;\n url.searchParams.set(key, Array.isArray(value) ? value.join(',') : String(value));\n }\n }\n return this.send<T>(url, { method: 'GET' });\n }\n\n post<T>(path: string, body: unknown): Promise<T> {\n return this.sendJson<T>('POST', path, body);\n }\n\n put<T>(path: string, body: unknown): Promise<T> {\n return this.sendJson<T>('PUT', path, body);\n }\n\n delete<T>(path: string, body?: unknown): Promise<T> {\n return this.sendJson<T>('DELETE', path, body);\n }\n\n /**\n * Absolute REST URL (incl. the `/v5` base) for a path — for building direct\n * browser-download links (e.g. the archive ZIP) that bypass `fetch`.\n */\n absoluteUrl(path: string): string {\n return `${this.baseUrl}${path}`;\n }\n\n /** The Filerobot key (`X-Filerobot-Key`), for query-param auth on direct downloads. */\n get authKey(): string | undefined {\n return (this.headers as Record<string, string>)['X-Filerobot-Key'];\n }\n\n private sendJson<T>(method: string, path: string, body: unknown): Promise<T> {\n return this.send<T>(new URL(`${this.baseUrl}${path}`), {\n method,\n headers: { 'Content-Type': 'application/json' },\n body: body === undefined ? undefined : JSON.stringify(body),\n });\n }\n\n private async send<T>(url: URL, init: RequestInit, retried = false): Promise<T> {\n const controller = new AbortController();\n const timeoutId = setTimeout(() => controller.abort(), TIMEOUT_MS);\n try {\n const response = await fetch(url.toString(), {\n ...init,\n headers: { ...this.headers, ...(init.headers as Record<string, string>) },\n signal: controller.signal,\n });\n clearTimeout(timeoutId);\n\n // A short-lived security-template key can expire mid-session — re-exchange\n // it once and retry, so the component recovers instead of 401-storming.\n if (response.status === 401 && !retried && this.auth?.mode === 'security-template') {\n const resolved = await resolveAuth(this.auth, this.apiDomain);\n this.headers = resolved.headers;\n return this.send<T>(url, init, true);\n }\n\n if (!response.ok) {\n let errorMsg = `API error: ${response.status} ${response.statusText}`;\n try {\n const body = await response.json();\n if (body?.msg) errorMsg = `API error: ${response.status} - ${body.msg}`;\n } catch {\n /* ignore parse errors */\n }\n throw new Error(errorMsg);\n }\n\n // 204 No Content (common for DELETE) has no body to parse.\n if (response.status === 204) return undefined as T;\n\n // Some gateways return an empty body on success (200/201) — tolerate it\n // instead of throwing a JSON parse error.\n const text = await response.text();\n if (!text) return undefined as T;\n const data = JSON.parse(text);\n if (data?.status === 'error') {\n throw new Error(`API error: ${data.msg || 'Unknown error'}`);\n }\n return data as T;\n } catch (err) {\n clearTimeout(timeoutId);\n if (err instanceof DOMException && err.name === 'AbortError') {\n throw new Error('API request timed out');\n }\n throw err;\n }\n }\n}\n","// FUTURE @scaleflex/dam-core — folders service. See ../README.md.\n//\n// Browse helpers (getFolders/createFolder) mirror @scaleflex/asset-picker.\n// rename/delete/move are light-dam's management layer, verified against\n// js-admin-react-filerobot-v5 (src/features/folders/folders.service.ts):\n// rename is `PUT /folder/{uuid}/name`; delete and move are bulk on `/folders`.\nimport type { DamClient } from './dam-client';\nimport type { Folder } from '../types/folder.types';\nimport type {\n GetFoldersResponse,\n RawFolder,\n ApiStatusResponse,\n CreateFolderResponse,\n} from '../types/api.types';\n\nexport interface GetFoldersParams {\n folderPath?: string;\n recursive?: number;\n limit?: number;\n offset?: number;\n q?: string;\n sortBy?: 'created_at' | 'modified_at' | 'name';\n sortDirection?: 'asc' | 'desc';\n}\n\nfunction normalizeFolder(raw: RawFolder): Folder {\n const path = raw.path ?? raw.name;\n const parentPath = path.replace(/\\/[^/]*$/, '') || '/';\n return {\n uuid: raw.uuid,\n name: raw.name.split('/').pop() || raw.name,\n path,\n parentPath,\n createdAt: raw.created_at,\n modifiedAt: raw.modified_at,\n };\n}\n\nexport async function getFolders(\n client: DamClient,\n params: GetFoldersParams = {},\n): Promise<Folder[]> {\n const sortField = params.sortBy ?? 'name';\n const sortDir = params.sortDirection ?? 'asc';\n const res = await client.get<GetFoldersResponse>('/folders', {\n folder: params.folderPath ?? '/',\n recursive: params.recursive ?? 0,\n limit: params.limit ?? 1000,\n offset: params.offset ?? 0,\n q: params.q,\n sort: `${sortField}:${sortDir}`,\n });\n return (res.folders ?? []).map(normalizeFolder);\n}\n\n/**\n * Create a folder. The backend's `name` is a full path; we build it from\n * parent + leaf. `parentFolderPath` is expected to start with `/`.\n */\nexport async function createFolder(\n client: DamClient,\n leafName: string,\n parentFolderPath: string,\n): Promise<Folder> {\n const cleanParent = parentFolderPath.replace(/\\/+$/, '');\n const fullPath = cleanParent ? `${cleanParent}/${leafName}` : `/${leafName}`;\n const res = await client.post<CreateFolderResponse>('/folders', { name: fullPath });\n if (res.status !== 'success' || !res.folder) throw new Error(res.msg || 'Failed to create folder');\n return normalizeFolder(res.folder);\n}\n\n// ── light-dam's management layer ─────────────────────────────────────────────\n\n// The response also carries the updated folder (Hub's `folders.slice.ts` reads\n// `response.folder` from this same endpoint), but it's discarded here — unlike\n// createFolder(), every current caller (BrowseStore.renameFolderById) recomputes the\n// new path itself and refetches from the server, so nothing depends on it. Return it\n// if a future caller needs to update state optimistically from this response instead.\nexport async function renameFolder(\n client: DamClient,\n uuid: string,\n newName: string,\n): Promise<void> {\n const res = await client.put<ApiStatusResponse>(`/folder/${encodeURIComponent(uuid)}/name`, {\n name: newName,\n });\n if (res?.status === 'error') throw new Error(res.msg || 'Failed to rename folder');\n}\n\nexport async function deleteFolders(client: DamClient, uuids: string[]): Promise<void> {\n const res = await client.delete<ApiStatusResponse>('/folders', { folders_uuids: uuids });\n if (res?.status === 'error') throw new Error(res.msg || 'Failed to delete folders');\n}\n\nexport async function moveFolders(\n client: DamClient,\n uuids: string[],\n destinationPath: string,\n): Promise<void> {\n const res = await client.post<ApiStatusResponse>('/folders', {\n folders_uuids: uuids,\n new_folder_name: destinationPath,\n });\n if (res?.status === 'error') throw new Error(res.msg || 'Failed to move folders');\n}\n","// FUTURE @scaleflex/dam-core — files service. See ../README.md.\n//\n// getFiles mirrors @scaleflex/asset-picker. rename/delete/move are light-dam's\n// management layer, verified against js-admin-react-filerobot-v5.\nimport type { DamClient } from './dam-client';\nimport type { Asset, AssetType } from '../types/asset.types';\nimport type { GetFilesResponse, RawFile, ApiStatusResponse } from '../types/api.types';\n\n/** File-type categories the backend can filter on (`type:\"image\"` …). */\nexport type FileTypeFilter = 'image' | 'video' | 'audio' | 'document' | 'other';\n\n/** Coarse file-size buckets (in bytes), mapped to a `size:\"min..max\"` clause. */\nexport type SizeBucket = 'small' | 'medium' | 'large' | 'huge';\n\n/** \"Within the last …\" presets for the created/modified date facets. */\nexport type DatePreset = 'today' | 'week' | 'month' | 'year';\n\n/**\n * User-specified media-duration bounds in **whole seconds**, mapped to a\n * `video_duration:\"min..max\"` clause. `null` = open-ended on that side; both ends\n * are **inclusive** (unlike the half-open SIZE_BUCKETS, which tile contiguously).\n */\nexport interface DurationRange {\n min: number | null;\n max: number | null;\n}\n\n/** Size-bucket bounds in bytes; `null` = open-ended on that side. */\nconst SIZE_BUCKETS: Record<SizeBucket, [number | null, number | null]> = {\n small: [null, 1_048_576], // < 1 MB\n medium: [1_048_576, 10_485_760], // 1–10 MB\n large: [10_485_760, 104_857_600], // 10–100 MB\n huge: [104_857_600, null], // > 100 MB\n};\n\n/** Lookback window per date preset, in days from the start of today. */\nconst DATE_PRESET_DAYS: Record<DatePreset, number> = {\n today: 0,\n week: 7,\n month: 30,\n year: 365,\n};\n\n/**\n * Lower-bound date for a preset, anchored to UTC midnight. Shared by the\n * server-side `created>\"…\"` clause and the client-side favourites matcher so the\n * two never disagree, and stable across paged fetches within a day (a per-page\n * `Date.now()` would creep forward and skip/dup items at the boundary).\n */\nfunction sinceMidnight(preset: DatePreset, nowMs: number): Date {\n const d = new Date(nowMs);\n d.setUTCHours(0, 0, 0, 0);\n d.setUTCDate(d.getUTCDate() - DATE_PRESET_DAYS[preset]);\n return d;\n}\n\nexport interface GetFilesParams {\n folder?: string;\n recursive?: number;\n limit?: number;\n offset?: number;\n search?: string;\n q?: string;\n /** Restrict to these file-type categories (Filerobot `type:\"…\"` clause). */\n types?: FileTypeFilter[];\n /** Restrict to a file-size bucket (`size:\"min..max\"` clause). */\n sizeBucket?: SizeBucket;\n /** Restrict to a media-duration range in seconds (`video_duration:\"min..max\"` clause). */\n durationRange?: DurationRange;\n /** Only assets created within this window (`created>\"…\"` clause). */\n createdWithin?: DatePreset;\n /** Only assets modified within this window (`updated>\"…\"` clause). */\n updatedWithin?: DatePreset;\n sortBy?: 'created_at' | 'modified_at' | 'name' | 'size' | 'type';\n sortDirection?: 'asc' | 'desc';\n /** Additive computed fields; `cdn_permalink` must be requested explicitly. */\n fields?: string[];\n}\n\nconst DEFAULT_API_FIELDS = ['cdn_permalink'];\n\nconst EXT: Record<string, AssetType> = {};\nconst addExt = (type: AssetType, exts: string) => exts.split(' ').forEach((e) => (EXT[e] = type));\naddExt('image', 'jpg jpeg png gif webp svg avif bmp tif tiff heic ico');\naddExt('video', 'mp4 webm mov m4v avi mkv ogv');\naddExt('audio', 'mp3 wav ogg oga m4a aac flac');\naddExt('document', 'pdf doc docx xls xlsx ppt pptx txt csv rtf md');\naddExt('model', 'glb gltf obj fbx stl usdz ply 3ds dae');\n\nfunction toAssetType(raw: RawFile): AssetType {\n const ext = (raw.name.split('.').pop() || '').toLowerCase();\n // 3D models usually have a generic MIME, so extension wins for them.\n if (EXT[ext] === 'model') return 'model';\n\n const t = (raw.file_type || raw.type || '').toLowerCase();\n if (t.startsWith('image')) return 'image';\n if (t.startsWith('video')) return 'video';\n if (t.startsWith('audio')) return 'audio';\n if (t.includes('pdf') || t.includes('document') || t.includes('text')) return 'document';\n\n return EXT[ext] ?? 'other';\n}\n\nfunction normalizeFile(raw: RawFile): Asset {\n return {\n uuid: raw.uuid,\n name: raw.name,\n folder: raw.folder ?? '/',\n type: toAssetType(raw),\n fileType: raw.file_type ?? raw.type,\n cdnUrl: raw.cdn_permalink ?? raw.cdn_url,\n thumbnailUrl:\n raw.info?.video_thumbnail ??\n raw.info?.image_thumbnail ??\n raw.info?.thumbnail ??\n raw.info?.preview,\n // Live API returns `size: { bytes, pretty }`; tolerate a plain number too.\n size: typeof raw.size === 'object' ? raw.size?.bytes : raw.size,\n duration: raw.info?.video_duration ?? raw.info?.duration,\n width: raw.width,\n height: raw.height,\n createdAt: raw.created_at,\n modifiedAt: raw.modified_at,\n };\n}\n\nfunction buildFilesQuery(params: GetFilesParams): Record<string, unknown> {\n const fields = params.fields ?? DEFAULT_API_FIELDS;\n const sortField = params.sortBy ?? 'created_at';\n const sortDir = params.sortDirection ?? 'desc';\n\n const parts: string[] = [];\n if (params.search) parts.push(params.search);\n if (params.q) parts.push(params.q);\n // Facet clauses — same notation js-admin-react-filerobot-v5 emits, space-joined.\n if (params.types?.length) parts.push(`type:${params.types.map((t) => `\"${t}\"`).join(',')}`);\n if (params.sizeBucket) {\n const [min, max] = SIZE_BUCKETS[params.sizeBucket];\n parts.push(`size:\"${min ?? ''}..${max ?? ''}\"`);\n }\n if (params.durationRange) {\n const { min, max } = params.durationRange;\n // An all-null range constrains nothing — emit no clause rather than `video_duration:\"..\"`.\n if (min != null || max != null) parts.push(`video_duration:\"${min ?? ''}..${max ?? ''}\"`);\n }\n // Date facets are an after-date bound on created/updated (\"Today\" = since the\n // start of today, not a rolling 24h window — see sinceMidnight).\n const now = Date.now();\n if (params.createdWithin)\n parts.push(`created>\"${sinceMidnight(params.createdWithin, now).toISOString()}\"`);\n if (params.updatedWithin)\n parts.push(`updated>\"${sinceMidnight(params.updatedWithin, now).toISOString()}\"`);\n\n return {\n offset: params.offset ?? 0,\n limit: params.limit ?? 100,\n format: 'json,regvar:api,select:internal',\n preview: 2301, // Filerobot preview-size preset for thumbnails\n recursive: params.recursive ?? 1,\n fields: fields.length ? fields : undefined,\n sort: `${sortField}:${sortDir}`,\n folder: params.folder,\n q: parts.length ? parts.join(' ') : undefined,\n };\n}\n\nexport interface FilesPage {\n assets: Asset[];\n /** Total matching the query (from the API when available, else this page's size). */\n total: number;\n}\n\n/** One page of files. `total` is the API's grand total when provided, else this page's size. */\nexport async function getFilesPage(\n client: DamClient,\n params: GetFilesParams = {},\n): Promise<FilesPage> {\n const query = buildFilesQuery(params);\n const res = await client.get<GetFilesResponse>('/files', query);\n const assets = (res.files ?? []).map(normalizeFile);\n const total = typeof res.total === 'number' ? res.total : (res.count ?? assets.length);\n return { assets, total };\n}\n\nexport async function getFiles(client: DamClient, params: GetFilesParams = {}): Promise<Asset[]> {\n return (await getFilesPage(client, params)).assets;\n}\n\n/** Subset of the facets that can also be evaluated client-side (favourites). */\nexport type FacetFilters = Pick<\n GetFilesParams,\n 'types' | 'sizeBucket' | 'durationRange' | 'createdWithin' | 'updatedWithin'\n>;\n\n/** Our AssetType → the FileTypeFilter bucket the `type` facet selects on. */\nconst TYPE_TO_FILTER: Record<AssetType, FileTypeFilter> = {\n image: 'image',\n video: 'video',\n audio: 'audio',\n document: 'document',\n model: 'other', // no dedicated 'model' facet — folds into \"Other\"\n archive: 'other',\n font: 'other',\n other: 'other',\n};\n\n/**\n * Does an already-loaded asset satisfy the facet filters? Used for the\n * Favourites axis, which fetches a known uuid set directly and filters in\n * memory rather than re-querying the server. Mirrors buildFilesQuery's clauses\n * (shares SIZE_BUCKETS / sinceMidnight) so server- and client-side agree.\n */\nexport function assetMatchesFacets(asset: Asset, f: FacetFilters, nowMs: number): boolean {\n if (f.types?.length && !f.types.includes(TYPE_TO_FILTER[asset.type])) return false;\n if (f.sizeBucket) {\n const [min, max] = SIZE_BUCKETS[f.sizeBucket];\n if (asset.size == null) return false;\n if (min != null && asset.size < min) return false;\n if (max != null && asset.size >= max) return false;\n }\n if (f.durationRange) {\n const { min, max } = f.durationRange;\n // Undurated assets are excluded server-side too, so drop them here for parity.\n if (asset.duration == null) return false;\n // Inclusive on both ends — a user-entered range, not a contiguous bucket.\n if (min != null && asset.duration < min) return false;\n if (max != null && asset.duration > max) return false;\n }\n if (f.createdWithin) {\n const t = asset.createdAt ? Date.parse(asset.createdAt) : NaN;\n if (!(t >= sinceMidnight(f.createdWithin, nowMs).getTime())) return false;\n }\n if (f.updatedWithin) {\n const t = asset.modifiedAt ? Date.parse(asset.modifiedAt) : NaN;\n if (!(t >= sinceMidnight(f.updatedWithin, nowMs).getTime())) return false;\n }\n return true;\n}\n\ntype SortField = NonNullable<GetFilesParams['sortBy']>;\n\n/** Client-side sort matching the server's `sort=field:dir`, for the favourites set. */\nexport function sortAssets(assets: Asset[], sortBy: SortField, dir: 'asc' | 'desc'): Asset[] {\n const mult = dir === 'asc' ? 1 : -1;\n const key = (a: Asset): string | number => {\n switch (sortBy) {\n case 'name':\n return a.name?.toLowerCase() ?? '';\n case 'size':\n return a.size ?? 0;\n case 'type':\n return a.type;\n case 'modified_at':\n return a.modifiedAt ? Date.parse(a.modifiedAt) : 0;\n default: // created_at\n return a.createdAt ? Date.parse(a.createdAt) : 0;\n }\n };\n return [...assets].sort((a, b) => {\n const ka = key(a);\n const kb = key(b);\n return ka < kb ? -mult : ka > kb ? mult : 0;\n });\n}\n\n/**\n * Fetch specific files by uuid (one `GET /file/{uuid}` each, bounded concurrency).\n * The Favourites axis is a known, usually-small uuid set, so this is far cheaper\n * than scanning the whole library. Missing/forbidden uuids are skipped; results\n * keep the input order (caller re-sorts as needed).\n */\nexport async function getFilesByUuids(\n client: DamClient,\n uuids: string[],\n opts: { concurrency?: number; fields?: string[] } = {},\n): Promise<Asset[]> {\n const fields = opts.fields ?? DEFAULT_API_FIELDS;\n const out: Array<Asset | undefined> = new Array(uuids.length);\n let next = 0;\n const worker = async (): Promise<void> => {\n while (next < uuids.length) {\n const i = next++;\n try {\n const res = await client.get<{ file?: RawFile }>(`/file/${encodeURIComponent(uuids[i])}`, {\n format: 'json,regvar:api,select:internal',\n preview: 2301,\n fields: fields.length ? fields : undefined,\n });\n if (res?.file) out[i] = normalizeFile(res.file);\n } catch {\n /* a deleted/forbidden favourite just drops out of the list */\n }\n }\n };\n const lanes = Math.min(opts.concurrency ?? 8, uuids.length);\n await Promise.all(Array.from({ length: lanes }, worker));\n return out.filter((a): a is Asset => a !== undefined);\n}\n\n// ── light-dam's management layer ─────────────────────────────────────────────\n// Endpoint shapes verified against js-admin-react-filerobot-v5\n// (src/features/files/files.service.ts): rename is per-file; delete and move are\n// bulk (uuid arrays in the body). The leading path segment is singular `/file`\n// for rename, plural `/files` for the bulk operations — matching the backend.\n\nexport async function renameFile(client: DamClient, uuid: string, newName: string): Promise<void> {\n const res = await client.put<ApiStatusResponse>(`/file/${encodeURIComponent(uuid)}/name`, {\n name: newName,\n });\n if (res?.status === 'error') throw new Error(res.msg || 'Failed to rename file');\n}\n\nexport async function deleteFiles(client: DamClient, uuids: string[]): Promise<void> {\n const res = await client.delete<ApiStatusResponse>('/files', { files_uuids: uuids });\n if (res?.status === 'error') throw new Error(res.msg || 'Failed to delete files');\n}\n\nexport async function moveFiles(\n client: DamClient,\n uuids: string[],\n destinationFolder: string,\n): Promise<void> {\n const res = await client.post<ApiStatusResponse>('/files', {\n files_uuids: uuids,\n new_folder_name: destinationFolder,\n });\n if (res?.status === 'error') throw new Error(res.msg || 'Failed to move files');\n}\n","// FUTURE @scaleflex/dam-core — labels service. See ../README.md.\n// Ported from @scaleflex/asset-picker. Labels are a cross-folder navigation axis;\n// they are simple tags (NOT metadata) so they fit within \"light\".\nimport type { DamClient } from './dam-client';\nimport type { Label } from '../types/label.types';\nimport type { ApiStatusResponse } from '../types/api.types';\n\ninterface GetLabelsResponse extends ApiStatusResponse {\n labels: Label[];\n}\n\nexport async function getLabels(client: DamClient): Promise<Label[]> {\n const res = await client.get<GetLabelsResponse>('/labels');\n return res.labels ?? [];\n}\n\n/**\n * Build the Files API query that filters by a label, mirroring asset-picker:\n * `q=labels:{sid}` with the leading \"#\" stripped from the label's sid.\n * Returns undefined for a label with no usable sid (caller skips the filter).\n */\nexport function labelFilterQuery(label: Label): string | undefined {\n if (!label.sid) return undefined;\n return `labels:${label.sid.replace(/^#/, '')}`;\n}\n","// @scaleflex/dam-core — file-variations assembly (UI-free, pure).\n//\n// Turns the raw `/variants` list + the container presets + the asset itself into a\n// single ordered `Variation[]` for the preview's Variations tab. Classification\n// mirrors js-admin-react-filerobot-v5 / portals-fe:\n// • Original — synthesized from the asset.\n// • Custom variants — `/variants` rows whose name is NOT a preset and that are not bg-removed.\n// • Background-removed — `/variants` rows with info.remove_background (kept, not preset-named).\n// • Preset variations — one virtual row per container preset, applied as `?p=<name>`.\n\nimport type { Asset } from '../types/asset.types';\nimport type { Variation, Preset } from '../types/variation.types';\nimport type { RawVariation } from '../types/api.types';\n\n/**\n * Apply a container preset to an original CDN URL by setting `?p=<name>` (replacing\n * any existing `p`). Returns the input unchanged if it isn't a valid URL.\n */\nexport function presetUrl(originalUrl: string, presetName: string): string {\n try {\n const url = new URL(originalUrl);\n url.searchParams.set('p', presetName);\n return url.toString();\n } catch {\n return originalUrl;\n }\n}\n\n/** Normalize one raw `/variants` row into a `Variation` (custom or background-removed). */\nexport function normalizeVariation(raw: RawVariation): Variation {\n return {\n uuid: raw.uuid,\n name: raw.name,\n url: raw.url,\n previewUrl: raw.preview ?? raw.url,\n width: raw.info?.img_w,\n height: raw.info?.img_h,\n size: raw.info?.size,\n format: raw.info?.img_type,\n isOriginal: false,\n isPreset: false,\n bgRemoved: raw.info?.remove_background === true,\n };\n}\n\n/** The Original variation, synthesized from the asset. */\nfunction originalVariation(asset: Asset): Variation {\n return {\n uuid: asset.uuid,\n name: asset.name,\n url: asset.cdnUrl ?? '',\n previewUrl: asset.thumbnailUrl ?? asset.cdnUrl,\n width: asset.width,\n height: asset.height,\n size: asset.size,\n format: asset.fileType,\n isOriginal: true,\n isPreset: false,\n bgRemoved: false,\n };\n}\n\n/** A virtual preset variation, derived from the asset's original URL. */\nfunction presetVariation(asset: Asset, preset: Preset): Variation {\n const base = asset.cdnUrl ?? '';\n return {\n uuid: `preset-${preset.name}`,\n name: preset.name,\n url: presetUrl(base, preset.name),\n previewUrl: presetUrl(asset.thumbnailUrl ?? base, preset.name),\n isOriginal: false,\n isPreset: true,\n bgRemoved: false,\n };\n}\n\nexport interface AssembleVariationsInput {\n asset: Asset;\n /** Raw rows from `getFileVariations`. */\n variants?: RawVariation[];\n /** Container presets from `getPresets`. */\n presets?: Preset[];\n}\n\n/**\n * Assemble the full Variations list: `[Original, …custom, …background-removed, …presets]`.\n * Preset-named raw variants are excluded from the custom set (they're represented by the\n * virtual preset rows instead), matching the reference admins' behaviour.\n */\nexport function assembleVariations({\n asset,\n variants = [],\n presets = [],\n}: AssembleVariationsInput): Variation[] {\n const presetNames = new Set(presets.map((p) => p.name));\n\n const custom = variants\n .filter((v) => !presetNames.has(v.name) && v.info?.remove_background !== true)\n .map(normalizeVariation);\n\n const bgRemoved = variants\n .filter((v) => v.info?.remove_background === true && !presetNames.has(v.name))\n .map(normalizeVariation);\n\n const presetRows = presets.map((p) => presetVariation(asset, p));\n\n return [originalVariation(asset), ...custom, ...bgRemoved, ...presetRows];\n}\n","// @scaleflex/dam-core — file variations + container presets service.\n// Endpoints verified against js-admin-react-filerobot-v5 / portals-fe:\n// GET /v5/file/{uuid}/variants → { variants: RawVariation[] }\n// GET /v5/settings → container config incl. cloudimg presets\nimport type { DamClient } from './dam-client';\nimport type { Asset } from '../types/asset.types';\nimport type { Variation, Preset } from '../types/variation.types';\nimport type {\n GetVariationsResponse,\n GetSettingsResponse,\n RawVariation,\n RawPreset,\n} from '../types/api.types';\nimport { assembleVariations } from '../helpers/variations';\n\n/** List a file's raw variations (custom + background-removed renditions). */\nexport async function getFileVariations(client: DamClient, uuid: string): Promise<RawVariation[]> {\n const res = await client.get<GetVariationsResponse>(\n `/file/${encodeURIComponent(uuid)}/variants`,\n );\n return res?.variants ?? [];\n}\n\n/** Normalize the various preset shapes (`string` | `{ name, desc }`) into `Preset[]`. */\nexport function normalizePresets(raw: RawPreset[] | undefined): Preset[] {\n if (!Array.isArray(raw)) return [];\n return raw\n .map((p): Preset | null =>\n typeof p === 'string'\n ? { name: p }\n : p && typeof p.name === 'string'\n ? { name: p.name, desc: p.desc }\n : null,\n )\n .filter((p): p is Preset => p !== null);\n}\n\n/**\n * Fetch the container's resolution presets from `/settings`. The presets array\n * lives under the container's cloudimg config; its exact nesting varies by\n * deployment, so we probe the known locations defensively (verify against a live\n * container — Q-AP presets).\n */\nexport async function getPresets(client: DamClient): Promise<Preset[]> {\n const res = await client.get<GetSettingsResponse>('/settings');\n const raw =\n res?.presets ?? res?.cloudimg?.presets ?? res?.settings?.cloudimg?.presets ?? undefined;\n return normalizePresets(raw);\n}\n\n/**\n * Convenience: fetch a file's variants + the container presets and assemble the\n * full ordered `Variation[]` (Original + custom + background-removed + presets).\n * Hosts that already hold variants/presets can call `assembleVariations` directly\n * instead (the fetch-or-inject contract).\n */\nexport async function loadVariations(client: DamClient, asset: Asset): Promise<Variation[]> {\n const [variants, presets] = await Promise.all([\n getFileVariations(client, asset.uuid),\n getPresets(client),\n ]);\n return assembleVariations({ asset, variants, presets });\n}\n","// @scaleflex/dam-core — server-side ZIP/bundle export (async archive job).\n// Contract verified against js-admin-react-filerobot-v5 (archive.thunks.js):\n// POST /file-archive { files_uuids, folders_uuids, urls } → { task_id }\n// GET /file-archive/{id}?status_only=1 → { task_status, task_percent }\n// GET /file-archive/{id}?secret_key=<key> → application/zip (after DONE)\n//\n// dam-core builds the URL + drives the poll; triggering the browser download (an\n// <a> click) stays in the UI layer (it is a DOM concern, and dam-core is UI-free).\n//\n// NOTE (Q-AP-ZIP): the path is built relative to the client's `/v5` base. Confirm\n// `file-archive` is reachable there for the target container — some deployments\n// expose it off the version-less root. Verify against a live container.\nimport type { DamClient } from './dam-client';\nimport type { CreateArchiveResponse, ArchiveStatusResponse } from '../types/api.types';\n\n/** What to bundle. Any combination of file uuids, folder uuids, and raw CDN URLs. */\nexport interface ArchiveTarget {\n filesUuids?: string[];\n foldersUuids?: string[];\n urls?: string[];\n}\n\n/** Kick off an archive job; resolves with its task id. */\nexport async function createArchive(client: DamClient, target: ArchiveTarget): Promise<string> {\n const res = await client.post<CreateArchiveResponse>('/file-archive', {\n files_uuids: target.filesUuids ?? [],\n folders_uuids: target.foldersUuids ?? [],\n urls: target.urls ?? [],\n });\n if (!res?.task_id) throw new Error('Archive task was not created');\n return res.task_id;\n}\n\n/** One status read of an archive job. */\nexport function getArchiveStatus(\n client: DamClient,\n taskId: string,\n): Promise<ArchiveStatusResponse> {\n return client.get<ArchiveStatusResponse>(`/file-archive/${encodeURIComponent(taskId)}`, {\n status_only: 1,\n });\n}\n\nconst IN_PROGRESS = new Set(['PENDING', 'RUNNING', 'PROCESSING', '']);\n\n/**\n * Interpret an archive status payload. Pure — the heart of the poll loop, tested\n * in isolation. Throws on a terminal failure status; returns `{done,progress}`\n * otherwise (`done` only for DONE).\n */\nexport function archiveStatusOutcome(res: ArchiveStatusResponse): {\n done: boolean;\n progress: number;\n} {\n const status = (res?.task_status ?? '').toUpperCase();\n const progress = typeof res?.task_percent === 'number' ? res.task_percent : 0;\n if (status === 'DONE') return { done: true, progress: 100 };\n if (IN_PROGRESS.has(status)) return { done: false, progress };\n throw new Error(`Archive job failed: ${res?.task_status ?? 'unknown status'}`);\n}\n\nexport interface PollArchiveOptions {\n /** Delay between status reads (default 1500ms). */\n intervalMs?: number;\n /** Give up after this long (default 120000ms). */\n timeoutMs?: number;\n /** Tolerate this many transient status-read errors (the job may not be registered yet). */\n maxRetries?: number;\n /** Abort the poll early. */\n signal?: AbortSignal;\n /** Progress callback (0–100). */\n onProgress?: (percent: number) => void;\n /** Injectable sleep (tests pass an immediate resolver). */\n sleep?: (ms: number) => Promise<void>;\n /** Injectable clock (tests pass a controllable one). */\n now?: () => number;\n}\n\n/** Poll an archive job until it reports DONE (or throws on failure/timeout/abort). */\nexport async function pollArchive(\n client: DamClient,\n taskId: string,\n opts: PollArchiveOptions = {},\n): Promise<void> {\n const interval = opts.intervalMs ?? 1500;\n const timeout = opts.timeoutMs ?? 120_000;\n const maxRetries = opts.maxRetries ?? 5;\n const sleep = opts.sleep ?? ((ms) => new Promise<void>((r) => setTimeout(r, ms)));\n const now = opts.now ?? Date.now;\n\n const deadline = now() + timeout;\n let retries = 0;\n\n for (;;) {\n if (opts.signal?.aborted) throw new Error('Archive polling aborted');\n if (now() > deadline) throw new Error('Archive polling timed out');\n\n let res: ArchiveStatusResponse;\n try {\n res = await getArchiveStatus(client, taskId);\n } catch (err) {\n // The job can lag behind its task id (404) or hit a transient blip — retry a bounded number of times.\n if (retries++ < maxRetries) {\n await sleep(interval);\n continue;\n }\n throw err;\n }\n\n const { done, progress } = archiveStatusOutcome(res);\n opts.onProgress?.(progress);\n if (done) return;\n await sleep(interval);\n }\n}\n\n/** Build the authenticated direct-download URL for a finished archive. */\nexport function archiveDownloadUrl(client: DamClient, taskId: string): string {\n const base = client.absoluteUrl(`/file-archive/${encodeURIComponent(taskId)}`);\n const key = client.authKey;\n if (!key) return base;\n const sep = base.includes('?') ? '&' : '?';\n return `${base}${sep}secret_key=${encodeURIComponent(key)}`;\n}\n\n/** Create → poll → resolve the authenticated download URL. */\nexport async function createAndPollArchive(\n client: DamClient,\n target: ArchiveTarget,\n opts: PollArchiveOptions = {},\n): Promise<string> {\n const taskId = await createArchive(client, target);\n await pollArchive(client, taskId, opts);\n return archiveDownloadUrl(client, taskId);\n}\n","// @scaleflex/dam-core — raw Filerobot browse endpoints.\n//\n// The \"raw\" counterpart to files.service / folders.service: these return the Filerobot wire\n// response un-normalized (FilerobotAsset / FilerobotFolder), for consumers that need full\n// fidelity — `@scaleflex/asset-picker` returns the raw object on select. Query-building mirrors\n// what asset-picker shipped (verified against the live API); the normalized getFilesPage /\n// getFolders remain the right choice for the headless core + light-dam. See plan/12 Wave 3.\nimport type { DamClient } from './dam-client';\nimport type { FilerobotAsset, FilerobotFolder } from '../types/filerobot.types';\n\n/** Computed Files API fields requested by default (signed CDN permalink). Additive on the wire. */\nconst DEFAULT_API_FIELDS = ['cdn_permalink'];\n\nexport interface FilerobotFilesParams {\n folder?: string;\n offset?: number;\n limit?: number;\n sort_by?: string;\n sort_direction?: string;\n /** Free-text search. */\n search?: string;\n /** Pre-serialized search/filter notation. */\n q?: string;\n recursive?: number;\n /** Enable AI-powered semantic search. */\n with_ai?: boolean;\n ai_query?: string;\n ai_lang?: string;\n /** Filter by collection. */\n collection_uuid?: string;\n /** Base64-encoded collection-folder filter. */\n f64?: string;\n /** Extra computed fields (e.g. `cdn_permalink`); undefined → the default set. */\n fields?: string[];\n /** Project-level toggle — `cdn_permalink` is stripped from `fields` when false. */\n permalinksEnabled?: boolean;\n}\n\nexport interface FilerobotFilesResponse {\n status: string;\n msg?: string;\n files: FilerobotAsset[];\n folders: FilerobotFolder[];\n base?: {\n count?: {\n files_direct?: number;\n files_recursive?: number;\n };\n };\n info?: {\n total_files_count?: number;\n total_files_size?: number;\n };\n limit: number;\n offset: number;\n}\n\nconst SORT_FIELDS: Record<string, string> = {\n created_at: 'created_at',\n modified_at: 'modified_at',\n name: 'name',\n size: 'size',\n type: 'type',\n};\n\nfunction buildFilesQuery(params: FilerobotFilesParams): Record<string, unknown> {\n let fields = params.fields ?? DEFAULT_API_FIELDS;\n if (!params.permalinksEnabled) {\n fields = fields.filter((field) => field !== 'cdn_permalink');\n }\n const apiParams: Record<string, unknown> = {\n offset: params.offset ?? 0,\n limit: params.limit ?? 100,\n format: 'json,regvar:api,select:internal',\n preview: 2301,\n recursive: params.recursive ?? 1,\n };\n if (fields.length > 0) apiParams.fields = fields;\n\n const sortField = params.sort_by ?? 'created_at';\n const sortDir = params.sort_direction ?? 'desc';\n apiParams.sort = `${SORT_FIELDS[sortField] || sortField}:${sortDir}`;\n\n if (params.folder) apiParams.folder = params.folder;\n\n // With AI search active the text goes in ai_query, not q.\n const parts: string[] = [];\n if (params.search && !params.with_ai) parts.push(params.search);\n if (params.q) parts.push(params.q);\n if (parts.length > 0) apiParams.q = parts.join(' ');\n\n if (params.with_ai) {\n apiParams.with_ai = true;\n if (params.ai_query) apiParams.ai_query = params.ai_query;\n if (params.ai_lang) apiParams.ai_lang = params.ai_lang;\n }\n if (params.collection_uuid) apiParams.collection_uuid = params.collection_uuid;\n if (params.f64) apiParams.f64 = params.f64;\n\n return apiParams;\n}\n\n/** Fetch a page of files as the raw Filerobot wire response (no normalization). */\nexport function getFilesRaw(\n client: DamClient,\n params: FilerobotFilesParams,\n): Promise<FilerobotFilesResponse> {\n return client.get<FilerobotFilesResponse>('/files', buildFilesQuery(params));\n}\n\nexport interface FilerobotFilesStatsResponse {\n status: string;\n stats?: {\n approx_files_count?: number;\n approx_files_size?: number;\n };\n info?: {\n total_files_count?: number;\n total_files_size?: number;\n };\n}\n\nexport type FilerobotFilesStatsParams = Pick<\n FilerobotFilesParams,\n 'folder' | 'q' | 'search' | 'recursive' | 'with_ai' | 'ai_query' | 'ai_lang' | 'collection_uuid' | 'f64'\n>;\n\n/** Approximate file count/size for a scope (the `/files/stats` endpoint). */\nexport function getFilesStats(\n client: DamClient,\n params: FilerobotFilesStatsParams,\n): Promise<FilerobotFilesStatsResponse> {\n const apiParams: Record<string, unknown> = { recursive: params.recursive ?? 1 };\n if (params.folder) apiParams.folder = params.folder;\n const parts: string[] = [];\n if (params.search && !params.with_ai) parts.push(params.search);\n if (params.q) parts.push(params.q);\n if (parts.length > 0) apiParams.q = parts.join(' ');\n if (params.with_ai) {\n apiParams.with_ai = true;\n if (params.ai_query) apiParams.ai_query = params.ai_query;\n if (params.ai_lang) apiParams.ai_lang = params.ai_lang;\n }\n if (params.collection_uuid) apiParams.collection_uuid = params.collection_uuid;\n if (params.f64) apiParams.f64 = params.f64;\n return client.get<FilerobotFilesStatsResponse>('/files/stats', apiParams);\n}\n\nexport interface FilerobotFoldersParams {\n folderPath?: string;\n recursive?: number;\n limit?: number;\n offset?: number;\n q?: string;\n sort_by?: string;\n sort_direction?: string;\n}\n\nexport interface FilerobotFoldersResponse {\n status: string;\n msg?: string;\n folders: FilerobotFolder[];\n total?: number;\n}\n\n/** Fetch folders as the raw Filerobot wire response (no normalization). */\nexport function getFoldersRaw(\n client: DamClient,\n params: FilerobotFoldersParams = {},\n): Promise<FilerobotFoldersResponse> {\n const apiParams: Record<string, unknown> = {\n recursive: params.recursive ?? 0,\n folder: params.folderPath ?? '/',\n limit: params.limit ?? 1000,\n offset: params.offset ?? 0,\n };\n if (params.q) apiParams.q = params.q;\n const sortField = params.sort_by ?? 'created_at';\n const sortDir = params.sort_direction ?? 'asc';\n apiParams.sort = `${SORT_FIELDS[sortField] || sortField}:${sortDir}`;\n return client.get<FilerobotFoldersResponse>('/folders', apiParams);\n}\n\nexport interface FolderPreview {\n file_uuid: string;\n file_uri_cdn: string;\n file_type: string;\n preview_origin: string;\n}\n\ninterface FolderPreviewsResponse {\n status: string;\n folders: Record<string, FolderPreview[]>;\n}\n\n/** Preview thumbnails for a set of folders (`POST /folders/previews`). */\nexport async function getFolderPreviews(\n client: DamClient,\n folderUuids: string[],\n): Promise<Record<string, FolderPreview[]>> {\n if (folderUuids.length === 0) return {};\n const res = await client.post<FolderPreviewsResponse>('/folders/previews', {\n folders: folderUuids,\n });\n return res.folders || {};\n}\n","// FUTURE @scaleflex/dam-core — headless browse store. See ../README.md.\n//\n// Framework-agnostic observable (subscribe/getState/actions) — ZERO Lit/React.\n// The UI layer adapts it (e.g. light-dam's Lit ReactiveController). Holds the\n// whole view/scope model documented in the repo root CLAUDE.md (§Views & UI model):\n// ONE content view (Assets) navigated by scope axes (folder | label | favourites).\nimport type { DamClient } from '../api/dam-client';\nimport {\n getFolders,\n createFolder,\n renameFolder,\n deleteFolders,\n moveFolders,\n} from '../api/folders.service';\nimport {\n getFilesPage,\n getFilesByUuids,\n assetMatchesFacets,\n sortAssets,\n renameFile,\n deleteFiles,\n moveFiles,\n} from '../api/files.service';\nimport type {\n FileTypeFilter,\n SizeBucket,\n DurationRange,\n DatePreset,\n FacetFilters,\n} from '../api/files.service';\nimport { getLabels, labelFilterQuery } from '../api/labels.service';\nimport type { Asset } from '../types/asset.types';\nimport type { Folder } from '../types/folder.types';\nimport type { Label } from '../types/label.types';\n\nexport type ScopeType = 'folder' | 'label' | 'favourites' | 'all';\nexport interface Scope {\n type: ScopeType;\n /** Folder path, label uuid, etc. — unused for 'favourites' / 'all'. */\n value?: string;\n}\n\nexport type ViewMode = 'grid' | 'list';\nexport type GridSize = 's' | 'm' | 'l';\nexport type SortBy = 'created_at' | 'modified_at' | 'name' | 'size' | 'type';\nexport type SortDirection = 'asc' | 'desc';\n\n/** Assets fetched per page; infinite scroll appends one page at a time. */\nexport const PAGE_SIZE = 100;\n\n/** Cap on favourites fetched by uuid, so a huge starred set can't fan out unbounded. */\nexport const MAX_FAVOURITES = 1000;\n\n/** The filter facets, as one object so adding a facet is a single-place change. */\nexport interface BrowseFilters {\n /** File-type categories (empty = all). */\n types: FileTypeFilter[];\n /** File-size bucket (null = any size). */\n size: SizeBucket | null;\n /** Media-duration range in seconds (null = any duration). */\n duration: DurationRange | null;\n /** \"Uploaded within\" on created date (null = any time). */\n created: DatePreset | null;\n /** \"Updated within\" on modified date (null = any time). */\n updated: DatePreset | null;\n}\n\nconst EMPTY_FILTERS: BrowseFilters = {\n types: [],\n size: null,\n duration: null,\n created: null,\n updated: null,\n};\n\nexport interface BrowseState {\n scope: Scope;\n viewMode: ViewMode;\n gridSize: GridSize;\n sortBy: SortBy;\n sortDirection: SortDirection;\n searchQuery: string;\n /** Active filter facets (type / size / duration / created / updated). */\n filters: BrowseFilters;\n folders: Folder[];\n labels: Label[];\n assets: Asset[];\n /** Total assets matching the current scope (from the API when available). */\n total: number;\n /** True when the Favourites scan stopped at its safety cap (rare). */\n truncated: boolean;\n /** True when more assets exist than are loaded — infinite scroll can fetch the next page. */\n hasMore: boolean;\n /** True while a `loadMore()` page is in flight (distinct from the initial `loading`). */\n loadingMore: boolean;\n /** Lazy folder-tree cache: folder path → its child folders. */\n treeChildren: Record<string, Folder[]>;\n /** Folder paths currently expanded in the sidebar tree. */\n expandedFolders: ReadonlySet<string>;\n /** Selected asset uuids — single or multi. */\n selection: ReadonlySet<string>;\n /** Favourited asset uuids (the per-user axis) — for star state in any scope. */\n favourites: ReadonlySet<string>;\n loading: boolean;\n error: string | null;\n}\n\nexport interface BrowseStoreOptions {\n rootFolder?: string;\n viewMode?: ViewMode;\n gridSize?: GridSize;\n sortBy?: SortBy;\n sortDirection?: SortDirection;\n /** Resolves favourite asset uuids (the only per-user axis). */\n favourites?: FavouritesAdapter;\n /** Called whenever the scope changes — lets the UI emit outward events. */\n onScopeChange?: (scope: Scope) => void;\n}\n\n/**\n * Pluggable persistence for the (per-user) Favourites axis.\n *\n * Filerobot v5 DOES have a native per-user favourites API\n * (`POST`/`DELETE /users/favorites` with `{ files_uuids }`), but light-dam keeps\n * this adapter so it doesn't bake in a user-identity/auth assumption: the default\n * is localStorage, and hosts (Portals/Spotlight) can inject an adapter backed by\n * the native API or their own store. Lives in core so it graduates to dam-core.\n */\nexport interface FavouritesAdapter {\n list(): Promise<string[]> | string[];\n add(uuid: string): Promise<void> | void;\n remove(uuid: string): Promise<void> | void;\n has(uuid: string): Promise<boolean> | boolean;\n}\n\ntype Listener = (state: BrowseState) => void;\n\nexport class BrowseStore {\n private state: BrowseState;\n private readonly listeners = new Set<Listener>();\n private readonly favAdapter?: FavouritesAdapter;\n private readonly onScopeChange?: (scope: Scope) => void;\n private loadSeq = 0;\n private treeSeq = 0;\n /** Raw count of assets fetched for the current scope — the infinite-scroll offset cursor. */\n private fetchedCount = 0;\n /** Last individually-selected asset — the pivot for shift-click range selection. */\n private selectionAnchor: string | null = null;\n\n constructor(\n private readonly client: DamClient,\n opts: BrowseStoreOptions = {},\n ) {\n this.favAdapter = opts.favourites;\n this.onScopeChange = opts.onScopeChange;\n this.state = {\n scope: { type: 'folder', value: opts.rootFolder ?? '/' },\n viewMode: opts.viewMode ?? 'grid',\n gridSize: opts.gridSize ?? 'm',\n sortBy: opts.sortBy ?? 'created_at',\n sortDirection: opts.sortDirection ?? 'desc',\n searchQuery: '',\n filters: { ...EMPTY_FILTERS },\n folders: [],\n labels: [],\n assets: [],\n total: 0,\n truncated: false,\n hasMore: false,\n loadingMore: false,\n treeChildren: {},\n expandedFolders: new Set(),\n selection: new Set(),\n favourites: new Set(),\n loading: false,\n error: null,\n };\n }\n\n /** Load sidebar metadata (labels, favourites, root folder tree) then the scope. */\n async init(): Promise<void> {\n await Promise.all([this.loadLabels(), this.loadFavourites(), this.loadFolderChildren('/')]);\n await this.load();\n }\n\n // ── folder tree (lazy) ──────────────────────────────────────────────────────\n\n /** Fetch and cache a folder's children (no-op if already cached). */\n async loadFolderChildren(path: string): Promise<void> {\n if (this.state.treeChildren[path]) return;\n try {\n const children = await getFolders(this.client, { folderPath: path });\n this.set({ treeChildren: { ...this.state.treeChildren, [path]: children } });\n } catch {\n /* tree is non-fatal; node just won't expand */\n }\n }\n\n /** Expand/collapse a folder node, lazy-loading its children on first expand. */\n async toggleFolder(path: string): Promise<void> {\n const expanded = new Set(this.state.expandedFolders);\n if (expanded.has(path)) {\n expanded.delete(path);\n this.set({ expandedFolders: expanded });\n } else {\n await this.loadFolderChildren(path);\n expanded.add(path);\n this.set({ expandedFolders: expanded });\n }\n }\n\n private async loadLabels(): Promise<void> {\n try {\n this.set({ labels: await getLabels(this.client) });\n } catch {\n /* labels are non-fatal; sidebar just omits them */\n }\n }\n\n private async loadFavourites(): Promise<void> {\n if (!this.favAdapter) return;\n try {\n this.set({ favourites: new Set(await this.favAdapter.list()) });\n } catch {\n /* ignore — favourites are best-effort */\n }\n }\n\n /** Toggle an asset's favourite status (per-user, via the adapter). */\n async toggleFavourite(uuid: string): Promise<void> {\n const isFav = this.state.favourites.has(uuid);\n const optimistic = new Set(this.state.favourites);\n if (isFav) optimistic.delete(uuid);\n else optimistic.add(uuid);\n this.set({ favourites: optimistic });\n try {\n if (this.favAdapter) {\n if (isFav) await this.favAdapter.remove(uuid);\n else await this.favAdapter.add(uuid);\n }\n } catch {\n // Revert ONLY this uuid against the current set, so a concurrent toggle of\n // another asset isn't clobbered.\n const reverted = new Set(this.state.favourites);\n if (isFav) reverted.add(uuid);\n else reverted.delete(uuid);\n this.set({ favourites: reverted });\n return;\n }\n if (this.state.scope.type === 'favourites') void this.load();\n }\n\n getState(): BrowseState {\n return this.state;\n }\n\n subscribe(listener: Listener): () => void {\n this.listeners.add(listener);\n return () => this.listeners.delete(listener);\n }\n\n private set(patch: Partial<BrowseState>): void {\n this.state = { ...this.state, ...patch };\n for (const l of this.listeners) l(this.state);\n }\n\n // ── navigation / scope ─────────────────────────────────────────────────────\n\n setScope(scope: Scope): void {\n this.selectionAnchor = null;\n this.set({ scope, selection: new Set() });\n this.onScopeChange?.(scope);\n void this.load();\n }\n\n openFolder(path: string): void {\n this.setScope({ type: 'folder', value: path });\n }\n\n openLabel(uuid: string): void {\n this.setScope({ type: 'label', value: uuid });\n }\n\n openFavourites(): void {\n this.setScope({ type: 'favourites' });\n }\n\n // ── view controls ──────────────────────────────────────────────────────────\n\n setViewMode(viewMode: ViewMode): void {\n this.set({ viewMode });\n }\n\n setGridSize(gridSize: GridSize): void {\n this.set({ gridSize });\n }\n\n setSort(sortBy: SortBy, sortDirection: SortDirection): void {\n this.set({ sortBy, sortDirection });\n void this.load();\n }\n\n setSearch(searchQuery: string): void {\n this.set({ searchQuery });\n void this.load();\n }\n\n /** Merge a partial facet patch into the active filters and reload from page 1. */\n setFilters(patch: Partial<BrowseFilters>): void {\n this.set({ filters: { ...this.state.filters, ...patch } });\n void this.load();\n }\n\n /** Toggle one file-type in the type facet. Reloads from page 1. */\n toggleType(type: FileTypeFilter): void {\n const types = this.state.filters.types;\n this.setFilters({\n types: types.includes(type) ? types.filter((t) => t !== type) : [...types, type],\n });\n }\n\n /** Number of active facets across all groups (for the toolbar badge). */\n get activeFilterCount(): number {\n const { types, size, duration, created, updated } = this.state.filters;\n return (\n types.length + (size ? 1 : 0) + (duration ? 1 : 0) + (created ? 1 : 0) + (updated ? 1 : 0)\n );\n }\n\n /** Clear every facet in one update, then reload. No-op if nothing is active. */\n clearFilters(): void {\n if (!this.activeFilterCount) return;\n this.set({ filters: { ...EMPTY_FILTERS } });\n void this.load();\n }\n\n // ── selection ──────────────────────────────────────────────────────────────\n\n toggleSelect(uuid: string, multi = false): void {\n const next = new Set(multi ? this.state.selection : []);\n const willSelect = !next.has(uuid);\n if (willSelect) next.add(uuid);\n else next.delete(uuid);\n // A selecting click becomes the pivot for a subsequent shift-click range; a\n // deselecting (toggle-off) click leaves the pivot put so it never points at an\n // item that's no longer selected.\n if (willSelect) this.selectionAnchor = uuid;\n this.set({ selection: next });\n }\n\n /**\n * Shift-click range selection: add every asset between the anchor (the last\n * individually-selected item) and `uuid` — in the current display order — to\n * the selection. Additive: existing selections are preserved, and the anchor\n * stays put so the range can be re-adjusted with another shift-click. With no\n * anchor yet, behaves like a plain multi-select toggle.\n */\n selectRange(uuid: string): void {\n const assets = this.state.assets;\n const toIdx = assets.findIndex((a) => a.uuid === uuid);\n if (toIdx < 0) return;\n const anchorIdx =\n this.selectionAnchor != null ? assets.findIndex((a) => a.uuid === this.selectionAnchor) : -1;\n if (anchorIdx < 0) {\n this.toggleSelect(uuid, true);\n return;\n }\n const [start, end] = anchorIdx <= toIdx ? [anchorIdx, toIdx] : [toIdx, anchorIdx];\n const next = new Set(this.state.selection);\n for (let i = start; i <= end; i++) next.add(assets[i].uuid);\n this.set({ selection: next });\n }\n\n /** Replace the whole selection in one update (e.g. \"select all\", marquee drag). */\n setSelection(uuids: Iterable<string>): void {\n const next = new Set(uuids);\n // Re-anchor to the last item so a following shift-click extends from the end of\n // this selection rather than a stale pivot from before it (null when empty).\n let last: string | null = null;\n for (const u of next) last = u;\n this.selectionAnchor = last;\n this.set({ selection: next });\n }\n\n clearSelection(): void {\n this.selectionAnchor = null;\n this.set({ selection: new Set() });\n }\n\n // ── mutations (light-dam's management layer) ───────────────────────────────\n // Orchestrated in core so the CRUD lives with dam-core, and every mutation\n // reloads the current scope. Endpoints verified against js-admin-react-filerobot-v5.\n\n /** Path of the folder currently in view (root when not in folder scope). */\n get currentFolderPath(): string {\n return this.state.scope.type === 'folder' ? (this.state.scope.value ?? '/') : '/';\n }\n\n /**\n * Folder mutations change the hierarchy — re-fetch the relevant tree nodes\n * (root, current, expanded) and swap them in atomically. Unlike a blank-then-\n * refill, this never shows an empty tree, keeps unrelated cached nodes, and a\n * `treeSeq` guard discards a superseded refresh.\n */\n private async refreshTree(): Promise<void> {\n const seq = ++this.treeSeq;\n const paths = new Set(['/', this.currentFolderPath, ...this.state.expandedFolders]);\n const updated: Record<string, Folder[]> = {};\n await Promise.all(\n [...paths].map(async (p) => {\n try {\n updated[p] = await getFolders(this.client, { folderPath: p });\n } catch {\n /* leave this node as-is */\n }\n }),\n );\n if (seq !== this.treeSeq) return;\n this.set({ treeChildren: { ...this.state.treeChildren, ...updated } });\n }\n\n private static parentOf(path: string): string {\n return path.replace(/\\/[^/]*$/, '') || '/';\n }\n\n /** Public: resolve a cached folder's path by uuid (e.g. for the move picker). */\n findFolderPath(uuid: string): string | undefined {\n return this.folderPath(uuid);\n }\n\n /** Find a cached folder's path by uuid (across the tree cache + current list). */\n private folderPath(uuid: string): string | undefined {\n for (const list of [this.state.folders, ...Object.values(this.state.treeChildren)]) {\n const found = list.find((f) => f.uuid === uuid);\n if (found) return found.path;\n }\n return undefined;\n }\n\n /**\n * After a folder is renamed/moved/deleted, keep the user out of a \"ghost\"\n * scope: if they're viewing the affected folder (or a descendant), remap the\n * scope to its new path (or the parent on delete). Returns true if it redirected\n * (which itself triggers a reload), so the caller can skip its own load().\n */\n private redirectScope(oldPath: string, newPath: string | null): boolean {\n const { scope } = this.state;\n if (scope.type !== 'folder' || !scope.value) return false;\n const cur = scope.value;\n if (cur !== oldPath && !cur.startsWith(oldPath + '/')) return false;\n if (newPath === null) {\n this.openFolder(BrowseStore.parentOf(oldPath));\n } else {\n this.openFolder(cur === oldPath ? newPath : newPath + cur.slice(oldPath.length));\n }\n return true;\n }\n\n async createFolder(name: string): Promise<void> {\n await createFolder(this.client, name, this.currentFolderPath);\n await this.refreshTree();\n await this.load();\n }\n\n async renameFolderById(uuid: string, name: string): Promise<void> {\n const oldPath = this.folderPath(uuid);\n await renameFolder(this.client, uuid, name);\n await this.refreshTree();\n const newPath = oldPath ? `${BrowseStore.parentOf(oldPath).replace(/\\/$/, '')}/${name}` : null;\n if (!(oldPath && newPath && this.redirectScope(oldPath, newPath))) await this.load();\n }\n\n async deleteFolderById(uuid: string): Promise<void> {\n const oldPath = this.folderPath(uuid);\n await deleteFolders(this.client, [uuid]);\n await this.refreshTree();\n if (!(oldPath && this.redirectScope(oldPath, null))) await this.load();\n }\n\n async moveFolderById(uuid: string, destination: string): Promise<void> {\n const oldPath = this.folderPath(uuid);\n await moveFolders(this.client, [uuid], destination);\n await this.refreshTree();\n const leaf = oldPath?.split('/').pop() ?? '';\n const newPath = oldPath ? `${destination.replace(/\\/$/, '')}/${leaf}` : null;\n if (!(oldPath && newPath && this.redirectScope(oldPath, newPath))) await this.load();\n }\n\n async renameAsset(uuid: string, name: string): Promise<void> {\n await renameFile(this.client, uuid, name);\n await this.load();\n }\n\n async deleteAssets(uuids: string[]): Promise<void> {\n await deleteFiles(this.client, uuids); // bulk endpoint — one request\n this.clearSelection();\n await this.load();\n }\n\n async moveAssets(uuids: string[], destination: string): Promise<void> {\n await moveFiles(this.client, uuids, destination); // bulk endpoint — one request\n this.clearSelection();\n await this.load();\n }\n\n // ── data loading ───────────────────────────────────────────────────────────\n\n /** Translate the active filter facets into the api query shape (undefined = off). */\n private facetParams(): FacetFilters {\n const { types, size, duration, created, updated } = this.state.filters;\n return {\n types: types.length ? types : undefined,\n sizeBucket: size ?? undefined,\n durationRange: duration ?? undefined,\n createdWithin: created ?? undefined,\n updatedWithin: updated ?? undefined,\n };\n }\n\n /**\n * Fetch one page of the current scope's assets starting at server `offset`.\n *\n * Folder/label/all are server-paged (offset + PAGE_SIZE) so infinite scroll\n * appends page by page. Favourites is a known uuid set, so it's fetched\n * directly by uuid (cheap, bounded by MAX_FAVOURITES) and filtered/sorted in\n * memory — no whole-library scan — and reports no further pages.\n *\n * `fetched` is the raw count returned by the server (before client dedupe), so\n * the caller advances its offset cursor correctly even when dedupe drops items.\n */\n private async fetchAssetsPage(offset: number): Promise<{\n assets: Asset[];\n total: number;\n truncated: boolean;\n hasMore: boolean;\n fetched: number;\n }> {\n const { scope, sortBy, sortDirection } = this.state;\n const search = this.state.searchQuery || undefined;\n const facets = this.facetParams();\n\n if (scope.type === 'favourites') {\n const wanted = [...this.state.favourites];\n const assets = await getFilesByUuids(this.client, wanted.slice(0, MAX_FAVOURITES));\n const now = Date.now();\n const matched = assets.filter((a) => assetMatchesFacets(a, facets, now));\n const sorted = sortAssets(matched, sortBy, sortDirection);\n return {\n assets: sorted,\n total: sorted.length,\n truncated: wanted.length > MAX_FAVOURITES,\n hasMore: false,\n fetched: 0,\n };\n }\n\n const base = { sortBy, sortDirection, search, ...facets, offset, limit: PAGE_SIZE };\n let page;\n if (scope.type === 'folder') {\n // With an active search, recurse into subfolders so matches deeper in the\n // tree surface too; otherwise list only the folder's direct children.\n const recursive = this.state.searchQuery.trim() ? 1 : 0;\n page = await getFilesPage(this.client, { ...base, folder: scope.value ?? '/', recursive });\n } else if (scope.type === 'label') {\n const label = this.state.labels.find((l) => l.uuid === scope.value);\n const q = label ? labelFilterQuery(label) : undefined;\n page = await getFilesPage(this.client, { ...base, recursive: 1, q });\n } else {\n page = await getFilesPage(this.client, { ...base, recursive: 1 });\n }\n // A full page means there may be more — the classic infinite-scroll signal,\n // robust even when the API omits a grand `total` (a short/empty next page\n // then stops it). `total` is kept only for the \"all N loaded\" tally.\n const hasMore = page.assets.length === PAGE_SIZE;\n return {\n assets: page.assets,\n total: page.total,\n truncated: false,\n hasMore,\n fetched: page.assets.length,\n };\n }\n\n /** Reload everything for the current scope from page 1. Stale responses are discarded. */\n async load(): Promise<void> {\n const seq = ++this.loadSeq;\n this.set({ loading: true, loadingMore: false, error: null });\n try {\n // The Folders sidebar stays usable in every scope: always list the children\n // of the current folder path (root for non-folder scopes), in parallel with\n // the scope's assets — so a label/favourites view never shows stale folders.\n const foldersPromise = getFolders(this.client, { folderPath: this.currentFolderPath });\n const pagePromise = this.fetchAssetsPage(0);\n\n const [folders, page] = await Promise.all([foldersPromise, pagePromise]);\n\n if (seq !== this.loadSeq) return; // a newer load superseded this one\n this.fetchedCount = page.fetched; // reset the infinite-scroll offset cursor\n this.set({\n folders,\n assets: page.assets,\n total: page.total,\n truncated: page.truncated,\n hasMore: page.hasMore,\n loading: false,\n // keep the tree cache warm for the folder we just listed\n treeChildren: { ...this.state.treeChildren, [this.currentFolderPath]: folders },\n });\n } catch (err) {\n if (seq !== this.loadSeq) return;\n this.set({ error: err instanceof Error ? err.message : String(err), loading: false });\n }\n }\n\n /**\n * Append the next page of assets for the current scope (infinite scroll).\n * No-op while already loading, when no more pages exist, or if the scope\n * changes mid-flight (the `loadSeq` guard discards a stale page).\n *\n * Offset comes from `fetchedCount` (raw items fetched), NOT the displayed\n * length, so dedupe dropping an item never re-requests the same page. Appended\n * assets are still de-duplicated by uuid so a shifting window can't double a card.\n */\n async loadMore(): Promise<void> {\n if (this.state.loading || this.state.loadingMore || !this.state.hasMore) return;\n const seq = this.loadSeq; // tie to the current load; a new load() bumps it and we bail\n this.set({ loadingMore: true });\n try {\n const page = await this.fetchAssetsPage(this.fetchedCount);\n if (seq !== this.loadSeq) return; // scope/filters changed during the fetch\n this.fetchedCount += page.fetched;\n const seen = new Set(this.state.assets.map((a) => a.uuid));\n const fresh = page.assets.filter((a) => !seen.has(a.uuid));\n this.set({\n assets: [...this.state.assets, ...fresh],\n total: page.total,\n hasMore: page.hasMore,\n loadingMore: false,\n error: null, // a recovered page clears any prior load-more failure\n });\n } catch (err) {\n if (seq !== this.loadSeq) return;\n // A failed page shouldn't wipe what's already shown — just stop and surface it.\n this.set({ error: err instanceof Error ? err.message : String(err), loadingMore: false });\n }\n }\n}\n","// FUTURE @scaleflex/dam-core — default Favourites persistence. See ../README.md.\n// localStorage-backed; the out-of-the-box adapter. Hosts with real user accounts\n// (Portals/Spotlight) inject their own per-user adapter instead.\nimport type { FavouritesAdapter } from './browse-store';\n\nexport class LocalFavouritesAdapter implements FavouritesAdapter {\n constructor(private readonly key = 'sfx-light-dam:favourites') {}\n\n private read(): Set<string> {\n try {\n const raw = localStorage.getItem(this.key);\n return new Set(raw ? (JSON.parse(raw) as string[]) : []);\n } catch {\n return new Set();\n }\n }\n\n private write(ids: Set<string>): void {\n try {\n localStorage.setItem(this.key, JSON.stringify([...ids]));\n } catch {\n /* storage unavailable / quota — favourites are best-effort locally */\n }\n }\n\n list(): string[] {\n return [...this.read()];\n }\n\n has(uuid: string): boolean {\n return this.read().has(uuid);\n }\n\n add(uuid: string): void {\n const ids = this.read();\n ids.add(uuid);\n this.write(ids);\n }\n\n remove(uuid: string): void {\n const ids = this.read();\n ids.delete(uuid);\n this.write(ids);\n }\n}\n","// FUTURE @scaleflex/dam-core — CDN/transform URL helpers. See ../README.md.\n// Mirrors @scaleflex/asset-picker's thumbnail/transform helpers.\n\nexport interface TransformParams {\n width?: number;\n height?: number;\n /** 1–100, or \"auto\". */\n quality?: number | 'auto';\n}\n\n/** Append Filerobot CDN sizing params (w/h/q) to a permalink, preserving existing params. */\nexport function addCdnParams(cdnUrl: string, params: TransformParams): string {\n const url = new URL(cdnUrl);\n if (params.width) url.searchParams.set('w', String(params.width));\n if (params.height) url.searchParams.set('h', String(params.height));\n if (params.quality !== undefined) url.searchParams.set('q', String(params.quality));\n return url.toString();\n}\n\n// ── transformed download / embed (mirrors js-admin-react-filerobot-v5) ─────────\n\n/** Output formats the transform pipeline supports. */\nexport type DownloadFormat = 'webp' | 'jpeg' | 'png' | 'gif';\n\n/** Quality presets → numeric `q` (JPEG/WEBP), matching the reference admin. */\nexport const QUALITY_LEVELS = { excellent: 90, good: 75, fair: 60 } as const;\nexport type QualityLevel = keyof typeof QUALITY_LEVELS;\n\n/** Standardized resize presets (max box, in px); \"Full HD\" + the reference's large/medium/small. */\nexport const RESIZE_PRESETS = { fullhd: 1920, large: 1200, medium: 600, small: 300 } as const;\nexport type ResizePreset = keyof typeof RESIZE_PRESETS;\n\n/** Animated formats need the dual cloudimage token so the still + animated paths both work. */\nconst FORMAT_TO_CLOUDIMG: Partial<Record<DownloadFormat, string>> = {\n webp: 'awebp,webp',\n gif: 'agif,gif',\n};\n\nexport interface TransformOptions {\n format?: DownloadFormat;\n /** Numeric q (1–100). Use QUALITY_LEVELS for the named presets. Omit for \"Original\" quality. */\n quality?: number;\n width?: number;\n height?: number;\n /**\n * Aspect-ratio handling when width/height are set (the embed/options \"lock\" toggle).\n * `true` → `func=bound` (fit inside the box, keep ratio); `false` → `func=cover`\n * (fill the box, may crop). Omit to leave `func` off (the reference admin's default\n * resize). Mirrors Portals' strict-size switch.\n */\n strictSizes?: boolean;\n}\n\nfunction applyTransform(url: URL, t: TransformOptions): void {\n // `download=1` on the source permalink bypasses transformations and returns the\n // original — drop it so format/quality/resize are actually honoured.\n url.searchParams.delete('download');\n if (t.width) url.searchParams.set('w', String(t.width));\n if (t.height) url.searchParams.set('h', String(t.height));\n if (t.quality != null) url.searchParams.set('q', String(t.quality));\n if (t.format) url.searchParams.set('force_format', FORMAT_TO_CLOUDIMG[t.format] ?? t.format);\n // Only meaningful alongside an explicit size; `bound` keeps ratio, `cover` fills/crops.\n if (t.strictSizes !== undefined && (t.width || t.height)) {\n url.searchParams.set('func', t.strictSizes ? 'bound' : 'cover');\n }\n}\n\n/** The downloaded file's name with the new extension (jpeg → jpg) when reformatting. */\nexport function transformedFileName(originalName: string, format?: DownloadFormat): string {\n if (!format) return originalName;\n const ext = format === 'jpeg' ? 'jpg' : format;\n // Strip the trailing extension, but never reduce to an empty base (\".htaccess\").\n const base = originalName.replace(/\\.[^./\\\\]+$/, '') || originalName;\n return `${base}.${ext}`;\n}\n\n/**\n * Build a CDN URL that serves the *transformed* asset as a download attachment.\n * Uses `force_download=<name>` (not `download=1`, which bypasses transforms and\n * returns the original) — same contract as the reference admin's getFileWithNewParams.\n */\nexport function buildTransformedDownloadUrl(\n cdnUrl: string,\n originalName: string,\n t: TransformOptions = {},\n): string {\n const url = new URL(cdnUrl);\n applyTransform(url, t);\n url.searchParams.set('force_download', transformedFileName(originalName, t.format));\n return url.toString();\n}\n\n/** Build a CDN URL for the transformed asset for embedding/linking (no download flag). */\nexport function buildEmbedUrl(cdnUrl: string, t: TransformOptions = {}): string {\n const url = new URL(cdnUrl);\n applyTransform(url, t);\n return url.toString();\n}\n","// FUTURE @scaleflex/dam-core — thumbnail + file-type icon helpers. See ../README.md.\n// Ported from @scaleflex/asset-picker (utils/thumbnail.ts, utils/file-type.ts):\n// CDN-generated thumbnails (video poster, PDF first page, sized images) on the\n// transform-capable `assets.filerobot.com` host, with hosted file-type icon\n// fallbacks for non-thumbnailable assets.\nimport type { Asset } from '../types/asset.types';\n\nconst FR_CDN = /^(https:\\/\\/)([a-z0-9_-]+)\\.filerobot\\.com\\/(?:v\\d+\\/)?/i;\n\n/**\n * Rewrite a CDN URL to the transform-capable `assets.filerobot.com/{token}/...`\n * host and strip any `/vN/` version prefix, so CDN image-processing params apply.\n */\nexport function getFormattedPreviewUrl(cdnUrl: string): string {\n if (!cdnUrl) return '';\n const stripVersion = (u: string) => u.replace(/(\\.filerobot\\.com\\/[a-z0-9_-]+)\\/v\\d+\\//i, '$1/');\n if (cdnUrl.includes('assets.filerobot.com')) return stripVersion(cdnUrl);\n const m = cdnUrl.match(FR_CDN);\n if (m) return stripVersion(cdnUrl.replace(FR_CDN, `$1assets.filerobot.com/${m[2]}/`));\n return cdnUrl;\n}\n\nfunction withParams(url: string, params: Record<string, string>): string {\n try {\n const u = new URL(url);\n for (const [k, v] of Object.entries(params)) u.searchParams.set(k, v);\n return u.toString();\n } catch {\n return url;\n }\n}\n\n/**\n * Best thumbnail URL for an asset, by type:\n * - video → API poster (`info.video_thumbnail`…) sized via CDN\n * - pdf → first page rendered as image (`doc_page=1`)\n * - image / has-API-thumbnail → sized CDN image\n * - audio / 3D / other → \"\" (caller shows a file-type icon)\n */\nexport function getThumbnailUrl(asset: Asset, size = 240): string {\n const w = String(size);\n\n if (asset.type === 'video') {\n if (!asset.thumbnailUrl) return '';\n return withParams(getFormattedPreviewUrl(asset.thumbnailUrl), { w, force_format: 'webp,jpeg' });\n }\n\n if (asset.type === 'document' && /\\.pdf$/i.test(asset.name)) {\n const cdn = getFormattedPreviewUrl(asset.cdnUrl ?? '');\n if (!cdn) return '';\n const stripped = cdn.replace(/([?&])func=proxy&?/, '$1').replace(/[?&]$/, '');\n return withParams(stripped, {\n w,\n force_format: 'webp,jpeg',\n doc_page: '1',\n bypass_process_proxy: '1',\n });\n }\n\n if (asset.type === 'image' || asset.thumbnailUrl) {\n const raw = asset.thumbnailUrl ?? asset.cdnUrl;\n if (!raw) return '';\n return withParams(getFormattedPreviewUrl(raw), { w, func: 'bound' });\n }\n\n return ''; // audio, model, generic documents → icon fallback\n}\n\n// ── file-type icons (hosted v3 SVG set, ported verbatim from asset-picker) ────\n\nconst FILE_TYPES_ICON_BASE = 'https://scaleflex.cloudimg.io/v7/assets/file-types/v3/';\n\n// asset-picker's set has no 3D icon — local override (inline isometric-cube SVG,\n// no remote dependency) for glb/gltf/obj/… so 3D assets aren't shown as GENERIC.\nconst MODEL_ICON_SVG =\n `<svg xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 48 48\" fill=\"none\">` +\n `<path d=\"M24 4 42 14v20L24 44 6 34V14z\" fill=\"#eef2ff\" stroke=\"#6366f1\" stroke-width=\"2\" stroke-linejoin=\"round\"/>` +\n `<path d=\"M24 4 42 14 24 24 6 14z\" fill=\"#c7d2fe\" stroke=\"#6366f1\" stroke-width=\"2\" stroke-linejoin=\"round\"/>` +\n `<path d=\"M24 24v20M24 24 6 14M24 24 42 14\" stroke=\"#6366f1\" stroke-width=\"2\" stroke-linejoin=\"round\"/>` +\n `</svg>`;\nconst MODEL_ICON = `data:image/svg+xml,${encodeURIComponent(MODEL_ICON_SVG)}`;\n\nconst FILE_TYPES_AND_ICONS: Record<string, string> = {\n _default: FILE_TYPES_ICON_BASE + 'GENERIC.svg?vh=9a518a',\n // Images\n png: FILE_TYPES_ICON_BASE + 'PNG.svg?vh=96cd9a',\n jpg: FILE_TYPES_ICON_BASE + 'JPG.svg?vh=06e819',\n jpg2: FILE_TYPES_ICON_BASE + 'JPG2.svg?vh=f0eb7f',\n jpeg: FILE_TYPES_ICON_BASE + 'JPEG.svg?vh=6a65e9',\n gif: FILE_TYPES_ICON_BASE + 'GIF.svg?vh=c3c2c3',\n bmp: FILE_TYPES_ICON_BASE + 'BMP.svg?vh=d2243a',\n webp: FILE_TYPES_ICON_BASE + 'WEBP.svg?vh=fedd74',\n svg: FILE_TYPES_ICON_BASE + 'SVG.svg?vh=a15e46',\n tiff: FILE_TYPES_ICON_BASE + 'TIFF.svg?vh=1f30c3',\n tif: FILE_TYPES_ICON_BASE + 'TIF.svg?vh=b383c9',\n heic: FILE_TYPES_ICON_BASE + 'HEIC.svg?vh=84adfe',\n avif: FILE_TYPES_ICON_BASE + 'AVIF.svg?vh=536b30',\n ico: FILE_TYPES_ICON_BASE + 'ICO.svg?vh=79063d',\n psd: FILE_TYPES_ICON_BASE + 'PSD.svg?vh=be6140',\n psb: FILE_TYPES_ICON_BASE + 'PSB.svg?vh=678646',\n ai: FILE_TYPES_ICON_BASE + 'AI.svg?vh=84b254',\n dwg: FILE_TYPES_ICON_BASE + 'DWG.svg?vh=971fb3',\n // Video\n mp4: FILE_TYPES_ICON_BASE + 'MP4.svg?vh=42f175',\n webm: FILE_TYPES_ICON_BASE + 'WEBM.svg?vh=26a84a',\n avi: FILE_TYPES_ICON_BASE + 'AVI.svg?vh=d22ba8',\n mpeg: FILE_TYPES_ICON_BASE + 'MPEG.svg?vh=ba93bb',\n ogv: FILE_TYPES_ICON_BASE + 'OGV.svg?vh=74d453',\n '3gp': FILE_TYPES_ICON_BASE + '3GP.svg?vh=f0d388',\n '3g2': FILE_TYPES_ICON_BASE + '3G2.svg?vh=04c652',\n swf: FILE_TYPES_ICON_BASE + 'SWF.svg?vh=3955e2',\n fla: FILE_TYPES_ICON_BASE + 'FLA.svg?vh=daf585',\n m3u8: FILE_TYPES_ICON_BASE + 'M3U8.svg?vh=7d5e62',\n // Audio\n mp3: FILE_TYPES_ICON_BASE + 'MP3.svg?vh=66bbef',\n wav: FILE_TYPES_ICON_BASE + 'WAV.svg?vh=d7a7d5',\n aac: FILE_TYPES_ICON_BASE + 'AAC.svg?vh=07f3f9',\n oga: FILE_TYPES_ICON_BASE + 'OGA.svg?vh=a5c622',\n opus: FILE_TYPES_ICON_BASE + 'OPUS.svg?vh=9548b1',\n weba: FILE_TYPES_ICON_BASE + 'WEBA.svg?vh=4dcf70',\n mid: FILE_TYPES_ICON_BASE + 'MID.svg?vh=3f0e29',\n midi: FILE_TYPES_ICON_BASE + 'MIDI.svg?vh=9fedec',\n cda: FILE_TYPES_ICON_BASE + 'CDA.svg?vh=85b83b',\n // Documents\n pdf: FILE_TYPES_ICON_BASE + 'PDF.svg?vh=18c5f7',\n doc: FILE_TYPES_ICON_BASE + 'DOC.svg?vh=d1b47c',\n docx: FILE_TYPES_ICON_BASE + 'DOCX.svg?vh=1eb6b0',\n txt: FILE_TYPES_ICON_BASE + 'TXT.svg?vh=307979',\n rtf: FILE_TYPES_ICON_BASE + 'RTF.svg?vh=978c5f',\n xls: FILE_TYPES_ICON_BASE + 'XLS.svg?vh=13b5f7',\n xlsx: FILE_TYPES_ICON_BASE + 'XLSX.svg?vh=79d64a',\n ppt: FILE_TYPES_ICON_BASE + 'PPT.svg?vh=4ee29b',\n pptx: FILE_TYPES_ICON_BASE + 'PPTX.svg?vh=8b1568',\n csv: FILE_TYPES_ICON_BASE + 'CSV.svg?vh=4add78',\n odt: FILE_TYPES_ICON_BASE + 'ODT.svg?vh=940781',\n ods: FILE_TYPES_ICON_BASE + 'ODS.svg?vh=9fbe9a',\n odp: FILE_TYPES_ICON_BASE + 'ODP.svg?vh=bf892d',\n dbf: FILE_TYPES_ICON_BASE + 'DBF.svg?vh=457bd4',\n vsd: FILE_TYPES_ICON_BASE + 'VSD.svg?vh=8a9ccb',\n abw: FILE_TYPES_ICON_BASE + 'ABW.svg?vh=313dc7',\n epub: FILE_TYPES_ICON_BASE + 'EPUB.svg?vh=15263d',\n azw: FILE_TYPES_ICON_BASE + 'AZW.svg?vh=a018b1',\n ics: FILE_TYPES_ICON_BASE + 'ICS.svg?vh=909f63',\n ogx: FILE_TYPES_ICON_BASE + 'OGX.svg?vh=f694d2',\n // Archives\n zip: FILE_TYPES_ICON_BASE + 'ZIP.svg?vh=84f98b',\n rar: FILE_TYPES_ICON_BASE + 'RAR.svg?vh=1d6423',\n '7z': FILE_TYPES_ICON_BASE + '7Z.svg?vh=e007e5',\n tar: FILE_TYPES_ICON_BASE + 'TAR.svg?vh=603aed',\n gz: FILE_TYPES_ICON_BASE + 'GZ.svg?vh=de13f7',\n bz: FILE_TYPES_ICON_BASE + 'BZ.svg?vh=0374ff',\n bz2: FILE_TYPES_ICON_BASE + 'BZ2.svg?vh=e14294',\n arc: FILE_TYPES_ICON_BASE + 'ARC.svg?vh=942fad',\n jar: FILE_TYPES_ICON_BASE + 'JAR.svg?vh=149796',\n mpkg: FILE_TYPES_ICON_BASE + 'MPKG.svg?vh=dea655',\n // Fonts\n ttf: FILE_TYPES_ICON_BASE + 'TTF.svg?vh=d2e2c1',\n otf: FILE_TYPES_ICON_BASE + 'OTF.svg?vh=c904fd',\n woff: FILE_TYPES_ICON_BASE + 'WOFF.svg?vh=4b8177',\n woff2: FILE_TYPES_ICON_BASE + 'WOFF2.svg?vh=b532d3',\n eot: FILE_TYPES_ICON_BASE + 'EOT.svg?vh=a54980',\n // Code / Scripts\n js: FILE_TYPES_ICON_BASE + 'JS.svg?vh=524691',\n mjs: FILE_TYPES_ICON_BASE + 'MJS.svg?vh=d57921',\n ts: FILE_TYPES_ICON_BASE + 'TS.svg?vh=9af3ae',\n css: FILE_TYPES_ICON_BASE + 'CSS.svg?vh=287863',\n html: FILE_TYPES_ICON_BASE + 'HTML.svg?vh=fa7a87',\n htm: FILE_TYPES_ICON_BASE + 'HTM.svg?vh=21323d',\n xhtml: FILE_TYPES_ICON_BASE + 'XHTML.svg?vh=e6d6a9',\n xul: FILE_TYPES_ICON_BASE + 'XUL.svg?vh=6c9c71',\n json: FILE_TYPES_ICON_BASE + 'JSON.svg?vh=104c9e',\n jsonld: FILE_TYPES_ICON_BASE + 'JSONLD.svg?vh=f30c0f',\n xml: FILE_TYPES_ICON_BASE + 'XML.svg?vh=7f7194',\n php: FILE_TYPES_ICON_BASE + 'PHP.svg?vh=503e36',\n sh: FILE_TYPES_ICON_BASE + 'SH.svg?vh=3b820e',\n csh: FILE_TYPES_ICON_BASE + 'CSH.svg?vh=08c0cc',\n // Executables / Disk images\n exe: FILE_TYPES_ICON_BASE + 'EXE.svg?vh=ccca53',\n iso: FILE_TYPES_ICON_BASE + 'ISO.svg?vh=064b8f',\n bin: FILE_TYPES_ICON_BASE + 'BIN.svg?vh=1e9618',\n // 3D models (local override — not in asset-picker's hosted set)\n glb: MODEL_ICON,\n gltf: MODEL_ICON,\n obj: MODEL_ICON,\n fbx: MODEL_ICON,\n stl: MODEL_ICON,\n usdz: MODEL_ICON,\n ply: MODEL_ICON,\n '3ds': MODEL_ICON,\n dae: MODEL_ICON,\n};\n\n/** Generic fallback icon (last resort for the <img> error cascade). */\nexport function getDefaultFileTypeIconUrl(): string {\n return FILE_TYPES_AND_ICONS._default;\n}\n\n/** File-type icon URL for a file name/extension; GENERIC when unmapped. */\nexport function getFileTypeIconUrl(name: string): string {\n const ext = (name.split('.').pop() || '').toLowerCase();\n return FILE_TYPES_AND_ICONS[ext] || FILE_TYPES_AND_ICONS._default;\n}\n","// Presentation formatters for asset metadata (size / date / dimensions). Pure,\n// dependency-free, and locale-aware — shared so every DAM widget renders the\n// same \"File info\" values. UI-free (returns plain strings); callers do the markup.\n\n/** Human-readable byte size (\"—\" when unknown, e.g. \"1.4 MB\", \"512 B\"). */\nexport function formatFileSize(bytes?: number | null): string {\n // Guard non-finite/NaN/negative too — `raw.size` flows in un-coerced from the API.\n if (bytes == null || !Number.isFinite(bytes) || bytes < 0) return '—';\n const units = ['B', 'KB', 'MB', 'GB', 'TB'];\n let value = bytes;\n let i = 0;\n while (value >= 1024 && i < units.length - 1) {\n value /= 1024;\n i++;\n }\n // Whole bytes; larger units show 1 decimal under 10, none at/above. Precision is\n // decided on the value rounded to 1dp so 9.95 prints \"10 KB\" (not \"10.0 KB\").\n const fmt = (v: number, unit: number) => {\n const dp = unit > 0 && Math.round(v * 10) / 10 < 10 ? 1 : 0;\n const str = v.toFixed(dp);\n return { str, rounded: Number(str) };\n };\n const first = fmt(value, i);\n let str = first.str;\n // A value that rounds up to 1024 rolls into the next unit (1024 KB → \"1.0 MB\").\n if (first.rounded >= 1024 && i < units.length - 1) {\n value /= 1024;\n i++;\n str = fmt(value, i).str;\n }\n return `${str} ${units[i]}`;\n}\n\n/** Short, locale-aware date (\"—\" when missing; echoes the raw value if unparseable). */\nexport function formatDate(iso?: string | null): string {\n if (!iso) return '—';\n const d = new Date(iso);\n if (Number.isNaN(d.getTime())) return iso;\n return d.toLocaleDateString(undefined, { year: 'numeric', month: 'short', day: 'numeric' });\n}\n\n/** \"W × H\" pixel dimensions, or \"—\" when either side is missing. */\nexport function formatDimensions(width?: number | null, height?: number | null): string {\n return width && height ? `${width} × ${height}` : '—';\n}\n","// Debounce a void-returning function: collapses bursts of calls into one,\n// firing `delay` ms after the last call. The returned function carries a\n// `.cancel()` to drop a pending trailing call (e.g. on disconnect). Shared so\n// every DAM widget debounces search/scroll/resize the same way.\n\n// eslint-disable-next-line @typescript-eslint/no-explicit-any\nexport function debounce<T extends (...args: any[]) => void>(\n fn: T,\n delay: number,\n): T & { cancel(): void } {\n let timer: ReturnType<typeof setTimeout>;\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n const debounced = ((...args: any[]) => {\n clearTimeout(timer);\n timer = setTimeout(() => fn(...args), delay);\n }) as T & { cancel(): void };\n debounced.cancel = () => clearTimeout(timer);\n return debounced;\n}\n"],"names":["DEFAULT_API_HOST","EXCHANGE_TIMEOUT_MS","DEFAULT_EXCHANGE_RETRIES","DEFAULT_EXCHANGE_RETRY_DELAY_MS","isTransientStatus","status","sleep","ms","resolve","getApiBase","container","apiDomain","exchangeSassKey","securityTemplateId","opts","url","retries","retryDelayMs","lastError","attempt","controller","timeoutId","response","data","err","buildAuthHeaders","auth","resolvedSassKey","headers","isAssetPickerAuth","normalizeAuth","resolveAuth","cfg","apiBase","sassKey","TIMEOUT_MS","DamClient","resolved","path","params","key","value","body","method","init","retried","_a","errorMsg","text","normalizeFolder","raw","parentPath","getFolders","client","sortField","sortDir","createFolder","leafName","parentFolderPath","cleanParent","fullPath","res","renameFolder","uuid","newName","deleteFolders","uuids","moveFolders","destinationPath","SIZE_BUCKETS","DATE_PRESET_DAYS","sinceMidnight","preset","nowMs","d","DEFAULT_API_FIELDS","EXT","addExt","type","exts","e","toAssetType","ext","t","normalizeFile","_b","_c","_d","_e","_f","_g","buildFilesQuery","fields","parts","min","max","now","getFilesPage","query","assets","total","getFiles","TYPE_TO_FILTER","assetMatchesFacets","asset","f","sortAssets","sortBy","dir","mult","a","b","ka","kb","getFilesByUuids","out","next","worker","i","lanes","renameFile","deleteFiles","moveFiles","destinationFolder","getLabels","labelFilterQuery","label","presetUrl","originalUrl","presetName","normalizeVariation","originalVariation","presetVariation","base","assembleVariations","variants","presets","presetNames","p","custom","v","bgRemoved","presetRows","getFileVariations","normalizePresets","getPresets","loadVariations","createArchive","target","getArchiveStatus","taskId","IN_PROGRESS","archiveStatusOutcome","progress","pollArchive","interval","timeout","maxRetries","r","deadline","done","archiveDownloadUrl","sep","createAndPollArchive","SORT_FIELDS","field","apiParams","getFilesRaw","getFilesStats","getFoldersRaw","getFolderPreviews","folderUuids","PAGE_SIZE","MAX_FAVOURITES","EMPTY_FILTERS","BrowseStore","children","expanded","isFav","optimistic","reverted","listener","patch","l","scope","viewMode","gridSize","sortDirection","searchQuery","types","size","duration","created","updated","multi","willSelect","toIdx","anchorIdx","start","end","last","u","seq","paths","list","found","oldPath","newPath","cur","name","destination","leaf","offset","search","facets","wanted","matched","sorted","page","recursive","q","hasMore","foldersPromise","pagePromise","folders","seen","fresh","LocalFavouritesAdapter","ids","addCdnParams","cdnUrl","QUALITY_LEVELS","RESIZE_PRESETS","FORMAT_TO_CLOUDIMG","applyTransform","transformedFileName","originalName","format","buildTransformedDownloadUrl","buildEmbedUrl","FR_CDN","getFormattedPreviewUrl","stripVersion","m","withParams","k","getThumbnailUrl","w","cdn","stripped","FILE_TYPES_ICON_BASE","MODEL_ICON_SVG","MODEL_ICON","FILE_TYPES_AND_ICONS","getDefaultFileTypeIconUrl","getFileTypeIconUrl","formatFileSize","bytes","units","fmt","unit","dp","str","first","formatDate","iso","formatDimensions","width","height","debounce","fn","delay","timer","debounced","args"],"mappings":"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"}
|