@ai-matrx/media 0.10.6 → 0.11.0

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/CHANGELOG.md CHANGED
@@ -1,5 +1,22 @@
1
1
  # @ai-matrx/media
2
2
 
3
+ ## 0.11.0 — file errors and the transport decision come from data
4
+
5
+ - `FileHandlerError` and `FileAccessDeniedError`, `FileNotFoundError`, `FileDeletedError`,
6
+ `ShareLinkInvalidError`, `ExternalFetchError`, `FileUploadError` are `@ai-matrx/data/files`'
7
+ classes, re-exported under the same names; `TUS_TRANSPORT_THRESHOLD_BYTES` /
8
+ `resolveUploadTransport` / `UploadTransport` likewise (architecture step 14). An error thrown by
9
+ data's file client is now `instanceof` media's classes.
10
+ - The base class's `name` is `"FileError"` (was `"FileHandlerError"`); subclasses keep their names.
11
+
12
+ Consumer action: none, unless code compares `error.name === "FileHandlerError"` (none found).
13
+
14
+ ## 0.10.7
15
+
16
+ Automatic changed-only republish (docs/metadata drift since the last tag — see
17
+ `git diff npm/media/v0.10.6..npm/media/v0.10.7 -- apps/shared/media`).
18
+ No source changes intended and no consumer action required.
19
+
3
20
  ## 0.10.6
4
21
 
5
22
  Automatic changed-only republish (docs/metadata drift since the last tag — see
@@ -39,16 +39,8 @@
39
39
  import { type UploadProgressEvent } from "../host/python-client.js";
40
40
  import type { AppDispatch } from "../host/store.js";
41
41
  import type { PermissionLevel, Visibility } from "../types.js";
42
- /**
43
- * Files at or above this size route through the resumable TUS transport
44
- * (`tusUpload.ts`); smaller files use the buffered multipart POST. Starting
45
- * value 80 MB — tune only on evidence. Callers can force either transport
46
- * via `CloudUploadOptions.transport`.
47
- */
48
- export declare const TUS_TRANSPORT_THRESHOLD_BYTES: number;
49
- export type UploadTransport = "buffered" | "tus";
50
- /** The ONE place the buffered-vs-TUS decision is made. */
51
- export declare function resolveUploadTransport(fileSizeBytes: number, override?: UploadTransport): UploadTransport;
42
+ export { resolveUploadTransport, TUS_TRANSPORT_THRESHOLD_BYTES, type UploadTransport, } from "@ai-matrx/data/files";
43
+ import { type UploadTransport } from "@ai-matrx/data/files";
52
44
  export interface CloudUploadOptions {
53
45
  /**
54
46
  * Full logical file path INCLUDING the filename. Use this when you
@@ -27,11 +27,11 @@ import {
27
27
  buildUploadMetadataEnvelope,
28
28
  tusUploadRaw
29
29
  } from "./tusUpload.js";
30
- const TUS_TRANSPORT_THRESHOLD_BYTES = 80 * 1024 * 1024;
31
- function resolveUploadTransport(fileSizeBytes, override) {
32
- if (override) return override;
33
- return fileSizeBytes >= TUS_TRANSPORT_THRESHOLD_BYTES ? "tus" : "buffered";
34
- }
30
+ import {
31
+ resolveUploadTransport,
32
+ TUS_TRANSPORT_THRESHOLD_BYTES
33
+ } from "@ai-matrx/data/files";
34
+ import { resolveUploadTransport as resolveUploadTransport2 } from "@ai-matrx/data/files";
35
35
  function isCloudUploadFailure(result) {
36
36
  return result.ok === false;
37
37
  }
@@ -112,7 +112,7 @@ async function cloudUploadRaw(file, rawOptions = {}) {
112
112
  }
113
113
  const filePath = resolveFilePath(file, options);
114
114
  const requestId = newRequestId();
115
- if (resolveUploadTransport(file.size, options.transport) === "tus") {
115
+ if (resolveUploadTransport2(file.size, options.transport) === "tus") {
116
116
  const tusResult = await tusUploadRaw(file, filePath, options, requestId);
117
117
  if (!tusResult.ok || !options.createShareLink) return tusResult;
118
118
  try {
@@ -216,7 +216,7 @@ async function cloudUpload(file, rawOptions, dispatch) {
216
216
  });
217
217
  try {
218
218
  let uploadData;
219
- if (resolveUploadTransport(file.size, options.transport) === "tus") {
219
+ if (resolveUploadTransport2(file.size, options.transport) === "tus") {
220
220
  const tusResult = await tusUploadRaw(
221
221
  file,
222
222
  filePath,
@@ -1 +1 @@
1
- {"version":3,"sources":["../../../../src/files/engine/upload/cloudUpload.ts"],"sourcesContent":["/**\n * features/files/upload/cloudUpload.ts\n *\n * THE single source of truth for file uploads in this app.\n *\n * ════════════════════════════════════════════════════════════════════════\n *\n * Why this exists\n * ───────────────\n * The single underlying upload primitive. Public callers go through the\n * universal file handler (`fileHandler.upload(...)` /\n * `useFileUpload`), which calls this. Server-side routes use\n * `Api.Server.uploadAndShare` which wraps the same Python `/files/upload`\n * endpoint with a server context.\n *\n * Rule: **every upload in the app goes through `cloudUpload` (this file)\n * via the universal handler, OR through `Api.Server.uploadAndShare`.**\n * Never call `supabase.from(\"cld_*\").upsert(...)` or `ensureFolderPath`\n * from a code path whose goal is \"upload a file.\" The Python backend\n * auto-creates any missing folders when you POST a full `file_path` —\n * the browser never needs to touch `cld_folders` directly. That\n * sidesteps the known RLS recursion bug AND keeps logic centralized.\n *\n * What this module owns:\n * • Resolving a logical `file_path` from caller-supplied options.\n * • Calling the Python `/files/upload` endpoint with progress.\n * • Optionally creating a permanent share link.\n * • Dispatching Redux upserts so the slice stays in sync.\n * • Returning a uniform `{ ok: true, ... } | { ok: false, error }` shape\n * — never null, never `[object Object]`, never silent.\n *\n * What this module does NOT do:\n * • Touch `supabase.from(\"cld_*\")` directly. Reads happen via the\n * SECURITY DEFINER tree RPC (`get_user_file_tree`) and\n * supabase realtime. Writes happen via the Python backend.\n * • Block on folder lookups. The backend handles folder creation\n * atomically as part of upload.\n */\n\nimport {\n uploadFile as Files_uploadFile,\n uploadFileWithProgress as Files_uploadFileWithProgress,\n} from \"../api/files\";\nimport {\n createShareLink as createCanonicalShareLink,\n shareLinkUrl,\n} from \"../host/share-links\";\nimport { pythonShareUrl } from \"../handler/utils/python-base\";\nimport {\n newRequestId,\n type ResponseMeta,\n type UploadProgressEvent,\n} from \"../host/python-client\";\nimport { extractErrorMessage } from \"@ai-matrx/data/net\";\nimport {\n registerRequest,\n releaseRequest,\n} from \"../redux/request-ledger\";\nimport {\n attachChildToFolder,\n trackUploadStart,\n updateUploadProgress,\n updateUploadStatus,\n upsertFile,\n} from \"../redux/slice\";\nimport { apiFileRecordToCloudFile } from \"../redux/converters\";\n// eslint-disable-next-line no-restricted-imports -- transport sibling INSIDE the ring-fenced upload internals; cloudUpload is the ONE transport-policy chokepoint\nimport {\n buildUploadMetadataEnvelope,\n tusUploadRaw,\n} from \"./tusUpload\";\nimport type { AppDispatch } from \"../host/store\";\nimport type { PermissionLevel, Visibility } from \"../types\";\n\n// ─── Transport policy ────────────────────────────────────────────────────\n\n/**\n * Files at or above this size route through the resumable TUS transport\n * (`tusUpload.ts`); smaller files use the buffered multipart POST. Starting\n * value 80 MB — tune only on evidence. Callers can force either transport\n * via `CloudUploadOptions.transport`.\n */\nexport const TUS_TRANSPORT_THRESHOLD_BYTES = 80 * 1024 * 1024;\n\nexport type UploadTransport = \"buffered\" | \"tus\";\n\n/** The ONE place the buffered-vs-TUS decision is made. */\nexport function resolveUploadTransport(\n fileSizeBytes: number,\n override?: UploadTransport,\n): UploadTransport {\n if (override) return override;\n return fileSizeBytes >= TUS_TRANSPORT_THRESHOLD_BYTES ? \"tus\" : \"buffered\";\n}\n\n// ─── Types ───────────────────────────────────────────────────────────────\n\nexport interface CloudUploadOptions {\n /**\n * Full logical file path INCLUDING the filename. Use this when you\n * want to control the exact name (e.g. server-generated \"{uuid}.jpg\").\n */\n filePath?: string;\n /**\n * Folder path (no filename). The browser appends `file.name`\n * automatically. The Python backend auto-creates any missing\n * folders. This is the **preferred** option for almost every caller.\n *\n * Example: `folderPath: \"Images/Chat\"` → uploaded path becomes\n * `\"Images/Chat/<file.name>\"`.\n */\n folderPath?: string;\n /**\n * Existing parentFolderId — only useful when you've already loaded the\n * folder via the tree RPC or realtime. Most callers should pass\n * `folderPath` instead so the backend handles folder creation.\n */\n parentFolderId?: string | null;\n visibility?: Visibility;\n shareWith?: string[];\n shareLevel?: PermissionLevel;\n changeSummary?: string;\n metadata?: Record<string, unknown>;\n /** Progress callback (XHR upload progress). */\n onProgress?: (event: UploadProgressEvent) => void;\n signal?: AbortSignal;\n /**\n * Transport override. Default: `resolveUploadTransport` picks TUS for\n * files ≥ `TUS_TRANSPORT_THRESHOLD_BYTES`, buffered otherwise.\n */\n transport?: UploadTransport;\n /**\n * If true, also creates a permanent share link after upload. Returns\n * the `shareUrl` (`/s/:token`) and the raw `shareToken`.\n */\n createShareLink?: boolean;\n /** Share-link permission — canonical `viewer` / `editor` levels. */\n shareLinkPermissionLevel?: \"viewer\" | \"editor\";\n shareLinkExpiresAt?: string | null;\n shareLinkMaxUses?: number | null;\n}\n\nexport interface CloudUploadSuccess {\n ok: true;\n fileId: string;\n filePath: string;\n fileSize: number | null;\n versionNumber: number;\n /** Backend-provided URL (storage URL — typically requires auth or signed). */\n url: string | null;\n shareToken?: string;\n /**\n * The PUBLIC LANDING PAGE for the share link, e.g.\n * `https://app.example.com/s/<token>`. Renders an HTML page with\n * file metadata and a download button. **Do NOT** use this for\n * `<img src>`, `<video src>`, or hot-linking — the page is HTML, not\n * the file bytes.\n */\n shareUrl?: string;\n /**\n * The PUBLIC DIRECT URL for the file bytes — points at Python's\n * `{BACKEND}/share/{token}` endpoint, which serves inline-safe bytes (and may\n * 302-redirect public files to their permanent CDN URL). Embed it in\n * `<img src>`, Slack/Notion unfurls, or OG images. No Next.js hop.\n *\n * Populated whenever `shareUrl` is — they always go in pairs because\n * both are derived from the same share token.\n */\n directUrl?: string;\n}\n\nexport interface CloudUploadFailure {\n ok: false;\n error: string;\n /** Backend error code if available (e.g. \"auth_required\", \"file_too_large\"). */\n errorCode?: string;\n /** Filename for caller reference. */\n fileName: string;\n}\n\nexport type CloudUploadResult = CloudUploadSuccess | CloudUploadFailure;\n\n/**\n * Type guard — when `result.ok` discriminator narrowing isn't enough for\n * TS (e.g. inside loops or where the union is inferred indirectly),\n * call this and the compiler will narrow the branches correctly.\n */\nexport function isCloudUploadFailure(\n result: CloudUploadResult,\n): result is CloudUploadFailure {\n return result.ok === false;\n}\n\nexport function isCloudUploadSuccess(\n result: CloudUploadResult,\n): result is CloudUploadSuccess {\n return result.ok === true;\n}\n\n// ─── Helpers ─────────────────────────────────────────────────────────────\n\nfunction resolveFilePath(file: File, options: CloudUploadOptions): string {\n if (options.filePath) return options.filePath.replace(/^\\/+/, \"\");\n if (options.folderPath) {\n const folder = options.folderPath.replace(/^\\/+|\\/+$/g, \"\");\n return folder ? `${folder}/${file.name}` : file.name;\n }\n return file.name;\n}\n\n/**\n * Build the public DIRECT-FILE URL for a share token — points at Python's\n * `/share/{token}` byte endpoint. No Next.js hop. Embed in\n * `<img src>`, `<video src>`, downloads, Slack unfurls, etc.\n */\nfunction buildDirectShareUrl(token: string): string {\n return pythonShareUrl(token);\n}\n\n/**\n * Mint a canonical share link (`platform.share_links`) for a freshly\n * uploaded file. Throws on failure so both upload paths surface the\n * distinct `share_link_failed` error.\n */\nasync function mintUploadShareLink(\n fileId: string,\n options: CloudUploadOptions,\n): Promise<{ shareToken: string; shareUrl: string; directUrl: string }> {\n const created = await createCanonicalShareLink({\n resourceType: \"file\",\n resourceId: fileId,\n permissionLevel: options.shareLinkPermissionLevel ?? \"viewer\",\n expiresAt: options.shareLinkExpiresAt ?? null,\n maxUses: options.shareLinkMaxUses ?? null,\n });\n if (!created.success || !created.token) {\n throw new Error(created.error ?? \"Share link creation failed\");\n }\n return {\n shareToken: created.token,\n shareUrl: shareLinkUrl(created.token),\n directUrl: buildDirectShareUrl(created.token),\n };\n}\n\n// ─── The upload organization — resolved ONCE, for every transport ────────\n\n/**\n * Bind the owning organization to an upload before a byte moves.\n *\n * 🚨 THE BUG THIS EXISTS FOR (2026-08-30). `InlineUploadArea` — the composer's\n * attach button — sent no organization at all. The server has honoured\n * `metadata.scope.organization_id` all along (membership-checked against\n * `iam.has_org_access_for`), but with nothing supplied it fell back to the\n * uploader's PERSONAL workspace. So a screenshot attached while no organization\n * was selected silently became a personal-workspace file; a minute later the\n * person picked their team organization, and the two no longer agreed.\n *\n * Fixing that one component would have been a patch on one of several upload\n * doors. This is the choke point instead: both transports and every caller go\n * through here, so an upload that forgets its organization is not possible\n * rather than merely unusual.\n *\n * If nothing is selected, the organization gate ASKS (one dialog, then the\n * upload continues into the chosen workspace). Cancelling throws\n * `OrganizationSelectionCancelled`, which the upload paths report as an\n * ordinary cancelled result — no bytes sent, nothing written, nothing to\n * clean up.\n */\nasync function bindUploadOrganization(\n options: CloudUploadOptions,\n): Promise<CloudUploadOptions> {\n const { ensureOrganizationContext } = await import(\n \"../host/org\"\n );\n const declared = organizationIdFromUploadMetadata(options.metadata);\n const organizationId = await ensureOrganizationContext({\n organizationId: declared,\n });\n return {\n ...options,\n metadata: {\n ...(options.metadata ?? {}),\n scope: {\n ...(typeof options.metadata?.scope === \"object\" &&\n options.metadata?.scope !== null\n ? (options.metadata.scope as Record<string, unknown>)\n : {}),\n organization_id: organizationId,\n },\n },\n };\n}\n\n/**\n * Turn a failed organization binding into an ordinary upload result.\n *\n * `cloudUpload`/`cloudUploadRaw` never throw — every caller reads\n * `{ ok: false, error }` — so a cancelled prompt keeps that contract. It is\n * marked `upload_cancelled` rather than an error code so the UI can stay silent:\n * the person did not fail at anything, they declined, and declining must return\n * them exactly where they were.\n */\nfunction uploadOrganizationFailure(\n err: unknown,\n fileName: string,\n): CloudUploadResult {\n const cancelled =\n err instanceof Error && err.name === \"OrganizationSelectionCancelled\";\n return {\n ok: false,\n error: cancelled\n ? \"Upload cancelled — no workspace was selected.\"\n : extractErrorMessage(err),\n errorCode: cancelled ? \"upload_cancelled\" : \"organization_required\",\n fileName,\n };\n}\n\n/** Read an organization a caller already declared, in either accepted shape. */\nexport function organizationIdFromUploadMetadata(\n metadata: Record<string, unknown> | undefined,\n): string | undefined {\n if (!metadata) return undefined;\n const direct = metadata.organization_id;\n if (typeof direct === \"string\" && direct.length > 0) return direct;\n const scope = metadata.scope;\n if (scope && typeof scope === \"object\") {\n const nested = (scope as Record<string, unknown>).organization_id;\n if (typeof nested === \"string\" && nested.length > 0) return nested;\n }\n return undefined;\n}\n\n// ─── Upload primitive (no Redux side effects) ────────────────────────────\n\n/**\n * Pure upload — POSTs to /files/upload with a full `file_path`. Backend\n * auto-creates folders. Use this when you need raw access without Redux\n * dispatches (e.g. server-side route handlers, isolated tests).\n *\n * For browser code that wants the file to appear in the UI, use\n * `cloudUpload` (with dispatch) instead.\n */\nexport async function cloudUploadRaw(\n file: File,\n rawOptions: CloudUploadOptions = {},\n): Promise<CloudUploadResult> {\n let options: CloudUploadOptions;\n try {\n options = await bindUploadOrganization(rawOptions);\n } catch (err) {\n return uploadOrganizationFailure(err, file.name);\n }\n const filePath = resolveFilePath(file, options);\n const requestId = newRequestId();\n\n // Transport policy: large files (or an explicit override) go resumable.\n if (resolveUploadTransport(file.size, options.transport) === \"tus\") {\n const tusResult = await tusUploadRaw(file, filePath, options, requestId);\n if (!tusResult.ok || !options.createShareLink) return tusResult;\n try {\n const link = await mintUploadShareLink(tusResult.fileId, options);\n return { ...tusResult, ...link };\n } catch (linkErr) {\n return {\n ok: false,\n error: `File uploaded but share link couldn't be created: ${extractErrorMessage(linkErr)}`,\n errorCode: \"share_link_failed\",\n fileName: file.name,\n };\n }\n }\n\n try {\n const params = {\n file,\n filePath,\n visibility: options.visibility ?? \"personal\",\n shareWith: options.shareWith,\n shareLevel: options.shareLevel,\n changeSummary: options.changeSummary,\n // Shared envelope builder — the SAME object the TUS transport encodes\n // into Upload-Metadata's `metadata_json` (parity is unit-tested).\n metadata: buildUploadMetadataEnvelope(options.metadata),\n };\n\n const upload = options.onProgress\n ? await Files_uploadFileWithProgress(params, options.onProgress, {\n requestId,\n signal: options.signal,\n // Reuse requestId as the idempotency key — single intended\n // upload from the FE perspective, single key on the BE. Backend\n // stores it in `metadata._idempotency_key` so retries don't\n // double-create version rows.\n idempotencyKey: requestId,\n })\n : await Files_uploadFile(params, {\n requestId,\n signal: options.signal,\n idempotencyKey: requestId,\n });\n\n let shareToken: string | undefined;\n let shareUrl: string | undefined;\n let directUrl: string | undefined;\n if (options.createShareLink) {\n try {\n // Always emit the direct-file URL alongside the page URL.\n // Consumers default to `directUrl` for img/video/audio/iframe\n // src; `shareUrl` is for \"click here to view file metadata\".\n ({ shareToken, shareUrl, directUrl } = await mintUploadShareLink(\n upload.data.file_id,\n options,\n ));\n } catch (linkErr) {\n // Upload succeeded; share-link creation didn't. Surface a\n // distinct error so the caller can decide whether to keep the\n // file or roll it back.\n return {\n ok: false,\n error: `File uploaded but share link couldn't be created: ${extractErrorMessage(linkErr)}`,\n errorCode: \"share_link_failed\",\n fileName: file.name,\n };\n }\n }\n\n return {\n ok: true,\n fileId: upload.data.file_id,\n filePath: upload.data.file_path,\n // Phase 0 rename — see docs/PYTHON_UPDATES.md §3.\n fileSize: upload.data.size_bytes,\n versionNumber: upload.data.version_number,\n url: upload.data.url ?? null,\n shareToken,\n shareUrl,\n directUrl,\n };\n } catch (err) {\n return {\n ok: false,\n error: extractErrorMessage(err),\n errorCode: (err as { code?: string } | null)?.code,\n fileName: file.name,\n };\n }\n}\n\n// ─── Upload with Redux side effects ──────────────────────────────────────\n\n/**\n * Upload a single file. THIS is the function 99% of callers want.\n *\n * Side effects:\n * • Dispatches `trackUploadStart`/`updateUploadProgress`/`updateUploadStatus`\n * so progress bars in the UI stay in sync.\n * • Dispatches `upsertFile` on success so the file appears in the slice\n * immediately (no need to wait for a tree refresh).\n * • Dispatches `attachChildToFolder` if `parentFolderId` is known.\n *\n * Returns the unified `CloudUploadResult`. Never throws — errors come\n * back as `{ ok: false, error }` so callers always have a clear path.\n */\nexport async function cloudUpload(\n file: File,\n rawOptions: CloudUploadOptions,\n dispatch: AppDispatch,\n): Promise<CloudUploadResult> {\n // Resolved BEFORE `trackUploadStart` so a cancelled organization prompt\n // leaves no orphan progress row in the slice — cancelling must look like the\n // upload never began, because it never did.\n let options: CloudUploadOptions;\n try {\n options = await bindUploadOrganization(rawOptions);\n } catch (err) {\n return uploadOrganizationFailure(err, file.name);\n }\n const filePath = resolveFilePath(file, options);\n const requestId = newRequestId();\n\n // 1. Mark the upload as pending in the slice.\n dispatch(\n trackUploadStart({\n requestId,\n fileName: file.name,\n fileSize: file.size,\n parentFolderId: options.parentFolderId ?? null,\n }),\n );\n registerRequest({\n requestId,\n kind: \"upload\",\n resourceId: null,\n resourceType: \"file\",\n });\n\n try {\n let uploadData: {\n file_id: string;\n file_path: string;\n size_bytes: number | null;\n checksum: string | null;\n version_number: number;\n url: string | null;\n };\n\n if (resolveUploadTransport(file.size, options.transport) === \"tus\") {\n // Resumable transport — same Redux progress/tracking as buffered.\n const tusResult = await tusUploadRaw(\n file,\n filePath,\n {\n ...options,\n onProgress: (ev) => {\n dispatch(\n updateUploadProgress({ requestId, bytesUploaded: ev.loaded }),\n );\n options.onProgress?.(ev);\n },\n },\n requestId,\n );\n if (!tusResult.ok) {\n dispatch(\n updateUploadStatus({\n requestId,\n status: \"error\",\n error: tusResult.error,\n }),\n );\n return tusResult;\n }\n uploadData = {\n file_id: tusResult.fileId,\n file_path: tusResult.filePath,\n size_bytes: tusResult.fileSize,\n checksum: null,\n version_number: tusResult.versionNumber,\n url: tusResult.url,\n };\n } else {\n const params = {\n file,\n filePath,\n visibility: options.visibility ?? \"personal\",\n shareWith: options.shareWith,\n shareLevel: options.shareLevel,\n changeSummary: options.changeSummary,\n // Shared envelope builder — identical object to the TUS transport's\n // `metadata_json` (parity is unit-tested).\n metadata: buildUploadMetadataEnvelope(options.metadata),\n };\n\n const upload = await Files_uploadFileWithProgress(\n params,\n (ev) => {\n dispatch(\n updateUploadProgress({\n requestId,\n bytesUploaded: ev.loaded,\n }),\n );\n options.onProgress?.(ev);\n },\n { requestId, signal: options.signal, idempotencyKey: requestId },\n );\n uploadData = {\n file_id: upload.data.file_id,\n file_path: upload.data.file_path,\n size_bytes: upload.data.size_bytes,\n checksum: upload.data.checksum ?? null,\n version_number: upload.data.version_number,\n url: upload.data.url ?? null,\n };\n }\n\n // 2. Slice upsert — file is now visible in the tree without\n // waiting for the realtime echo or a refetch.\n dispatch(\n upsertFile(\n apiFileRecordToCloudFile({\n id: uploadData.file_id,\n owner_id: \"\",\n file_path: uploadData.file_path,\n file_name: uploadData.file_path.split(\"/\").pop() ?? file.name,\n mime_type: file.type || null,\n // Phase 0 rename — see docs/PYTHON_UPDATES.md §3.\n size_bytes: uploadData.size_bytes,\n checksum: uploadData.checksum,\n visibility: options.visibility ?? \"personal\",\n current_version: uploadData.version_number,\n parent_folder_id: options.parentFolderId ?? null,\n metadata: options.metadata ?? {},\n created_at: null,\n updated_at: null,\n deleted_at: null,\n }),\n ),\n );\n if (options.parentFolderId) {\n dispatch(\n attachChildToFolder({\n parentFolderId: options.parentFolderId,\n kind: \"file\",\n id: uploadData.file_id,\n }),\n );\n }\n dispatch(\n updateUploadStatus({\n requestId,\n status: \"success\",\n fileId: uploadData.file_id,\n }),\n );\n\n // 3. Optional share link.\n let shareToken: string | undefined;\n let shareUrl: string | undefined;\n let directUrl: string | undefined;\n if (options.createShareLink) {\n try {\n // Always emit the direct-file URL alongside the page URL.\n // Consumers default to `directUrl` for img/video/audio/iframe\n // src; `shareUrl` is for \"click here to view file metadata\".\n ({ shareToken, shareUrl, directUrl } = await mintUploadShareLink(\n uploadData.file_id,\n options,\n ));\n } catch (linkErr) {\n return {\n ok: false,\n error: `File uploaded but share link couldn't be created: ${extractErrorMessage(linkErr)}`,\n errorCode: \"share_link_failed\",\n fileName: file.name,\n };\n }\n }\n\n return {\n ok: true,\n fileId: uploadData.file_id,\n filePath: uploadData.file_path,\n // Phase 0 rename — see docs/PYTHON_UPDATES.md §3.\n fileSize: uploadData.size_bytes,\n versionNumber: uploadData.version_number,\n url: uploadData.url,\n shareToken,\n shareUrl,\n directUrl,\n };\n } catch (err) {\n const message = extractErrorMessage(err);\n dispatch(\n updateUploadStatus({\n requestId,\n status: \"error\",\n error: message,\n }),\n );\n return {\n ok: false,\n error: message,\n errorCode: (err as { code?: string } | null)?.code,\n fileName: file.name,\n };\n } finally {\n releaseRequest(requestId);\n }\n}\n\n// ─── Batch upload ────────────────────────────────────────────────────────\n\nexport interface CloudUploadManyOptions extends CloudUploadOptions {\n /** Parallel ceiling. Defaults to 3. */\n concurrency?: number;\n}\n\nexport interface CloudUploadManyResult {\n successes: CloudUploadSuccess[];\n failures: CloudUploadFailure[];\n}\n\n/**\n * Upload multiple files with bounded concurrency. Returns a structured\n * result so callers can show \"3 of 5 uploaded\" UI cleanly.\n */\nexport async function cloudUploadMany(\n files: File[],\n options: CloudUploadManyOptions,\n dispatch: AppDispatch,\n): Promise<CloudUploadManyResult> {\n const limit = Math.max(1, options.concurrency ?? 3);\n const successes: CloudUploadSuccess[] = [];\n const failures: CloudUploadFailure[] = [];\n const queue = [...files];\n\n async function worker(): Promise<void> {\n while (queue.length) {\n const file = queue.shift();\n if (!file) return;\n const result = await cloudUpload(file, options, dispatch);\n if (isCloudUploadSuccess(result)) {\n successes.push(result);\n } else {\n failures.push(result);\n }\n }\n }\n\n await Promise.all(\n Array.from({ length: Math.min(limit, files.length) }, worker),\n );\n\n return { successes, failures };\n}\n\n/**\n * Imperative shortcut for non-React code that still wants Redux side\n * effects. Pulls the dispatch from the store singleton.\n */\nexport async function cloudUploadImperative(\n file: File,\n options: CloudUploadOptions,\n): Promise<CloudUploadResult> {\n const { getStore } = await import(\"../host/store\");\n const store = getStore();\n if (!store) {\n return {\n ok: false,\n error: \"Redux store is not ready\",\n errorCode: \"store_not_ready\",\n fileName: file.name,\n };\n }\n return cloudUpload(file, options, store.dispatch as AppDispatch);\n}\n"],"mappings":"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","names":[]}
1
+ {"version":3,"sources":["../../../../src/files/engine/upload/cloudUpload.ts"],"sourcesContent":["/**\n * features/files/upload/cloudUpload.ts\n *\n * THE single source of truth for file uploads in this app.\n *\n * ════════════════════════════════════════════════════════════════════════\n *\n * Why this exists\n * ───────────────\n * The single underlying upload primitive. Public callers go through the\n * universal file handler (`fileHandler.upload(...)` /\n * `useFileUpload`), which calls this. Server-side routes use\n * `Api.Server.uploadAndShare` which wraps the same Python `/files/upload`\n * endpoint with a server context.\n *\n * Rule: **every upload in the app goes through `cloudUpload` (this file)\n * via the universal handler, OR through `Api.Server.uploadAndShare`.**\n * Never call `supabase.from(\"cld_*\").upsert(...)` or `ensureFolderPath`\n * from a code path whose goal is \"upload a file.\" The Python backend\n * auto-creates any missing folders when you POST a full `file_path` —\n * the browser never needs to touch `cld_folders` directly. That\n * sidesteps the known RLS recursion bug AND keeps logic centralized.\n *\n * What this module owns:\n * • Resolving a logical `file_path` from caller-supplied options.\n * • Calling the Python `/files/upload` endpoint with progress.\n * • Optionally creating a permanent share link.\n * • Dispatching Redux upserts so the slice stays in sync.\n * • Returning a uniform `{ ok: true, ... } | { ok: false, error }` shape\n * — never null, never `[object Object]`, never silent.\n *\n * What this module does NOT do:\n * • Touch `supabase.from(\"cld_*\")` directly. Reads happen via the\n * SECURITY DEFINER tree RPC (`get_user_file_tree`) and\n * supabase realtime. Writes happen via the Python backend.\n * • Block on folder lookups. The backend handles folder creation\n * atomically as part of upload.\n */\n\nimport {\n uploadFile as Files_uploadFile,\n uploadFileWithProgress as Files_uploadFileWithProgress,\n} from \"../api/files\";\nimport {\n createShareLink as createCanonicalShareLink,\n shareLinkUrl,\n} from \"../host/share-links\";\nimport { pythonShareUrl } from \"../handler/utils/python-base\";\nimport {\n newRequestId,\n type ResponseMeta,\n type UploadProgressEvent,\n} from \"../host/python-client\";\nimport { extractErrorMessage } from \"@ai-matrx/data/net\";\nimport {\n registerRequest,\n releaseRequest,\n} from \"../redux/request-ledger\";\nimport {\n attachChildToFolder,\n trackUploadStart,\n updateUploadProgress,\n updateUploadStatus,\n upsertFile,\n} from \"../redux/slice\";\nimport { apiFileRecordToCloudFile } from \"../redux/converters\";\n// eslint-disable-next-line no-restricted-imports -- transport sibling INSIDE the ring-fenced upload internals; cloudUpload is the ONE transport-policy chokepoint\nimport {\n buildUploadMetadataEnvelope,\n tusUploadRaw,\n} from \"./tusUpload\";\nimport type { AppDispatch } from \"../host/store\";\nimport type { PermissionLevel, Visibility } from \"../types\";\n\n// ─── Transport policy ────────────────────────────────────────────────────\n\n// The buffered-vs-TUS decision has ONE home, `@ai-matrx/data/files` (architecture step 14).\nexport {\n resolveUploadTransport,\n TUS_TRANSPORT_THRESHOLD_BYTES,\n type UploadTransport,\n} from \"@ai-matrx/data/files\";\nimport { resolveUploadTransport, type UploadTransport } from \"@ai-matrx/data/files\";\n\n// ─── Types ───────────────────────────────────────────────────────────────\n\nexport interface CloudUploadOptions {\n /**\n * Full logical file path INCLUDING the filename. Use this when you\n * want to control the exact name (e.g. server-generated \"{uuid}.jpg\").\n */\n filePath?: string;\n /**\n * Folder path (no filename). The browser appends `file.name`\n * automatically. The Python backend auto-creates any missing\n * folders. This is the **preferred** option for almost every caller.\n *\n * Example: `folderPath: \"Images/Chat\"` → uploaded path becomes\n * `\"Images/Chat/<file.name>\"`.\n */\n folderPath?: string;\n /**\n * Existing parentFolderId — only useful when you've already loaded the\n * folder via the tree RPC or realtime. Most callers should pass\n * `folderPath` instead so the backend handles folder creation.\n */\n parentFolderId?: string | null;\n visibility?: Visibility;\n shareWith?: string[];\n shareLevel?: PermissionLevel;\n changeSummary?: string;\n metadata?: Record<string, unknown>;\n /** Progress callback (XHR upload progress). */\n onProgress?: (event: UploadProgressEvent) => void;\n signal?: AbortSignal;\n /**\n * Transport override. Default: `resolveUploadTransport` picks TUS for\n * files ≥ `TUS_TRANSPORT_THRESHOLD_BYTES`, buffered otherwise.\n */\n transport?: UploadTransport;\n /**\n * If true, also creates a permanent share link after upload. Returns\n * the `shareUrl` (`/s/:token`) and the raw `shareToken`.\n */\n createShareLink?: boolean;\n /** Share-link permission — canonical `viewer` / `editor` levels. */\n shareLinkPermissionLevel?: \"viewer\" | \"editor\";\n shareLinkExpiresAt?: string | null;\n shareLinkMaxUses?: number | null;\n}\n\nexport interface CloudUploadSuccess {\n ok: true;\n fileId: string;\n filePath: string;\n fileSize: number | null;\n versionNumber: number;\n /** Backend-provided URL (storage URL — typically requires auth or signed). */\n url: string | null;\n shareToken?: string;\n /**\n * The PUBLIC LANDING PAGE for the share link, e.g.\n * `https://app.example.com/s/<token>`. Renders an HTML page with\n * file metadata and a download button. **Do NOT** use this for\n * `<img src>`, `<video src>`, or hot-linking — the page is HTML, not\n * the file bytes.\n */\n shareUrl?: string;\n /**\n * The PUBLIC DIRECT URL for the file bytes — points at Python's\n * `{BACKEND}/share/{token}` endpoint, which serves inline-safe bytes (and may\n * 302-redirect public files to their permanent CDN URL). Embed it in\n * `<img src>`, Slack/Notion unfurls, or OG images. No Next.js hop.\n *\n * Populated whenever `shareUrl` is — they always go in pairs because\n * both are derived from the same share token.\n */\n directUrl?: string;\n}\n\nexport interface CloudUploadFailure {\n ok: false;\n error: string;\n /** Backend error code if available (e.g. \"auth_required\", \"file_too_large\"). */\n errorCode?: string;\n /** Filename for caller reference. */\n fileName: string;\n}\n\nexport type CloudUploadResult = CloudUploadSuccess | CloudUploadFailure;\n\n/**\n * Type guard — when `result.ok` discriminator narrowing isn't enough for\n * TS (e.g. inside loops or where the union is inferred indirectly),\n * call this and the compiler will narrow the branches correctly.\n */\nexport function isCloudUploadFailure(\n result: CloudUploadResult,\n): result is CloudUploadFailure {\n return result.ok === false;\n}\n\nexport function isCloudUploadSuccess(\n result: CloudUploadResult,\n): result is CloudUploadSuccess {\n return result.ok === true;\n}\n\n// ─── Helpers ─────────────────────────────────────────────────────────────\n\nfunction resolveFilePath(file: File, options: CloudUploadOptions): string {\n if (options.filePath) return options.filePath.replace(/^\\/+/, \"\");\n if (options.folderPath) {\n const folder = options.folderPath.replace(/^\\/+|\\/+$/g, \"\");\n return folder ? `${folder}/${file.name}` : file.name;\n }\n return file.name;\n}\n\n/**\n * Build the public DIRECT-FILE URL for a share token — points at Python's\n * `/share/{token}` byte endpoint. No Next.js hop. Embed in\n * `<img src>`, `<video src>`, downloads, Slack unfurls, etc.\n */\nfunction buildDirectShareUrl(token: string): string {\n return pythonShareUrl(token);\n}\n\n/**\n * Mint a canonical share link (`platform.share_links`) for a freshly\n * uploaded file. Throws on failure so both upload paths surface the\n * distinct `share_link_failed` error.\n */\nasync function mintUploadShareLink(\n fileId: string,\n options: CloudUploadOptions,\n): Promise<{ shareToken: string; shareUrl: string; directUrl: string }> {\n const created = await createCanonicalShareLink({\n resourceType: \"file\",\n resourceId: fileId,\n permissionLevel: options.shareLinkPermissionLevel ?? \"viewer\",\n expiresAt: options.shareLinkExpiresAt ?? null,\n maxUses: options.shareLinkMaxUses ?? null,\n });\n if (!created.success || !created.token) {\n throw new Error(created.error ?? \"Share link creation failed\");\n }\n return {\n shareToken: created.token,\n shareUrl: shareLinkUrl(created.token),\n directUrl: buildDirectShareUrl(created.token),\n };\n}\n\n// ─── The upload organization — resolved ONCE, for every transport ────────\n\n/**\n * Bind the owning organization to an upload before a byte moves.\n *\n * 🚨 THE BUG THIS EXISTS FOR (2026-08-30). `InlineUploadArea` — the composer's\n * attach button — sent no organization at all. The server has honoured\n * `metadata.scope.organization_id` all along (membership-checked against\n * `iam.has_org_access_for`), but with nothing supplied it fell back to the\n * uploader's PERSONAL workspace. So a screenshot attached while no organization\n * was selected silently became a personal-workspace file; a minute later the\n * person picked their team organization, and the two no longer agreed.\n *\n * Fixing that one component would have been a patch on one of several upload\n * doors. This is the choke point instead: both transports and every caller go\n * through here, so an upload that forgets its organization is not possible\n * rather than merely unusual.\n *\n * If nothing is selected, the organization gate ASKS (one dialog, then the\n * upload continues into the chosen workspace). Cancelling throws\n * `OrganizationSelectionCancelled`, which the upload paths report as an\n * ordinary cancelled result — no bytes sent, nothing written, nothing to\n * clean up.\n */\nasync function bindUploadOrganization(\n options: CloudUploadOptions,\n): Promise<CloudUploadOptions> {\n const { ensureOrganizationContext } = await import(\n \"../host/org\"\n );\n const declared = organizationIdFromUploadMetadata(options.metadata);\n const organizationId = await ensureOrganizationContext({\n organizationId: declared,\n });\n return {\n ...options,\n metadata: {\n ...(options.metadata ?? {}),\n scope: {\n ...(typeof options.metadata?.scope === \"object\" &&\n options.metadata?.scope !== null\n ? (options.metadata.scope as Record<string, unknown>)\n : {}),\n organization_id: organizationId,\n },\n },\n };\n}\n\n/**\n * Turn a failed organization binding into an ordinary upload result.\n *\n * `cloudUpload`/`cloudUploadRaw` never throw — every caller reads\n * `{ ok: false, error }` — so a cancelled prompt keeps that contract. It is\n * marked `upload_cancelled` rather than an error code so the UI can stay silent:\n * the person did not fail at anything, they declined, and declining must return\n * them exactly where they were.\n */\nfunction uploadOrganizationFailure(\n err: unknown,\n fileName: string,\n): CloudUploadResult {\n const cancelled =\n err instanceof Error && err.name === \"OrganizationSelectionCancelled\";\n return {\n ok: false,\n error: cancelled\n ? \"Upload cancelled — no workspace was selected.\"\n : extractErrorMessage(err),\n errorCode: cancelled ? \"upload_cancelled\" : \"organization_required\",\n fileName,\n };\n}\n\n/** Read an organization a caller already declared, in either accepted shape. */\nexport function organizationIdFromUploadMetadata(\n metadata: Record<string, unknown> | undefined,\n): string | undefined {\n if (!metadata) return undefined;\n const direct = metadata.organization_id;\n if (typeof direct === \"string\" && direct.length > 0) return direct;\n const scope = metadata.scope;\n if (scope && typeof scope === \"object\") {\n const nested = (scope as Record<string, unknown>).organization_id;\n if (typeof nested === \"string\" && nested.length > 0) return nested;\n }\n return undefined;\n}\n\n// ─── Upload primitive (no Redux side effects) ────────────────────────────\n\n/**\n * Pure upload — POSTs to /files/upload with a full `file_path`. Backend\n * auto-creates folders. Use this when you need raw access without Redux\n * dispatches (e.g. server-side route handlers, isolated tests).\n *\n * For browser code that wants the file to appear in the UI, use\n * `cloudUpload` (with dispatch) instead.\n */\nexport async function cloudUploadRaw(\n file: File,\n rawOptions: CloudUploadOptions = {},\n): Promise<CloudUploadResult> {\n let options: CloudUploadOptions;\n try {\n options = await bindUploadOrganization(rawOptions);\n } catch (err) {\n return uploadOrganizationFailure(err, file.name);\n }\n const filePath = resolveFilePath(file, options);\n const requestId = newRequestId();\n\n // Transport policy: large files (or an explicit override) go resumable.\n if (resolveUploadTransport(file.size, options.transport) === \"tus\") {\n const tusResult = await tusUploadRaw(file, filePath, options, requestId);\n if (!tusResult.ok || !options.createShareLink) return tusResult;\n try {\n const link = await mintUploadShareLink(tusResult.fileId, options);\n return { ...tusResult, ...link };\n } catch (linkErr) {\n return {\n ok: false,\n error: `File uploaded but share link couldn't be created: ${extractErrorMessage(linkErr)}`,\n errorCode: \"share_link_failed\",\n fileName: file.name,\n };\n }\n }\n\n try {\n const params = {\n file,\n filePath,\n visibility: options.visibility ?? \"personal\",\n shareWith: options.shareWith,\n shareLevel: options.shareLevel,\n changeSummary: options.changeSummary,\n // Shared envelope builder — the SAME object the TUS transport encodes\n // into Upload-Metadata's `metadata_json` (parity is unit-tested).\n metadata: buildUploadMetadataEnvelope(options.metadata),\n };\n\n const upload = options.onProgress\n ? await Files_uploadFileWithProgress(params, options.onProgress, {\n requestId,\n signal: options.signal,\n // Reuse requestId as the idempotency key — single intended\n // upload from the FE perspective, single key on the BE. Backend\n // stores it in `metadata._idempotency_key` so retries don't\n // double-create version rows.\n idempotencyKey: requestId,\n })\n : await Files_uploadFile(params, {\n requestId,\n signal: options.signal,\n idempotencyKey: requestId,\n });\n\n let shareToken: string | undefined;\n let shareUrl: string | undefined;\n let directUrl: string | undefined;\n if (options.createShareLink) {\n try {\n // Always emit the direct-file URL alongside the page URL.\n // Consumers default to `directUrl` for img/video/audio/iframe\n // src; `shareUrl` is for \"click here to view file metadata\".\n ({ shareToken, shareUrl, directUrl } = await mintUploadShareLink(\n upload.data.file_id,\n options,\n ));\n } catch (linkErr) {\n // Upload succeeded; share-link creation didn't. Surface a\n // distinct error so the caller can decide whether to keep the\n // file or roll it back.\n return {\n ok: false,\n error: `File uploaded but share link couldn't be created: ${extractErrorMessage(linkErr)}`,\n errorCode: \"share_link_failed\",\n fileName: file.name,\n };\n }\n }\n\n return {\n ok: true,\n fileId: upload.data.file_id,\n filePath: upload.data.file_path,\n // Phase 0 rename — see docs/PYTHON_UPDATES.md §3.\n fileSize: upload.data.size_bytes,\n versionNumber: upload.data.version_number,\n url: upload.data.url ?? null,\n shareToken,\n shareUrl,\n directUrl,\n };\n } catch (err) {\n return {\n ok: false,\n error: extractErrorMessage(err),\n errorCode: (err as { code?: string } | null)?.code,\n fileName: file.name,\n };\n }\n}\n\n// ─── Upload with Redux side effects ──────────────────────────────────────\n\n/**\n * Upload a single file. THIS is the function 99% of callers want.\n *\n * Side effects:\n * • Dispatches `trackUploadStart`/`updateUploadProgress`/`updateUploadStatus`\n * so progress bars in the UI stay in sync.\n * • Dispatches `upsertFile` on success so the file appears in the slice\n * immediately (no need to wait for a tree refresh).\n * • Dispatches `attachChildToFolder` if `parentFolderId` is known.\n *\n * Returns the unified `CloudUploadResult`. Never throws — errors come\n * back as `{ ok: false, error }` so callers always have a clear path.\n */\nexport async function cloudUpload(\n file: File,\n rawOptions: CloudUploadOptions,\n dispatch: AppDispatch,\n): Promise<CloudUploadResult> {\n // Resolved BEFORE `trackUploadStart` so a cancelled organization prompt\n // leaves no orphan progress row in the slice — cancelling must look like the\n // upload never began, because it never did.\n let options: CloudUploadOptions;\n try {\n options = await bindUploadOrganization(rawOptions);\n } catch (err) {\n return uploadOrganizationFailure(err, file.name);\n }\n const filePath = resolveFilePath(file, options);\n const requestId = newRequestId();\n\n // 1. Mark the upload as pending in the slice.\n dispatch(\n trackUploadStart({\n requestId,\n fileName: file.name,\n fileSize: file.size,\n parentFolderId: options.parentFolderId ?? null,\n }),\n );\n registerRequest({\n requestId,\n kind: \"upload\",\n resourceId: null,\n resourceType: \"file\",\n });\n\n try {\n let uploadData: {\n file_id: string;\n file_path: string;\n size_bytes: number | null;\n checksum: string | null;\n version_number: number;\n url: string | null;\n };\n\n if (resolveUploadTransport(file.size, options.transport) === \"tus\") {\n // Resumable transport — same Redux progress/tracking as buffered.\n const tusResult = await tusUploadRaw(\n file,\n filePath,\n {\n ...options,\n onProgress: (ev) => {\n dispatch(\n updateUploadProgress({ requestId, bytesUploaded: ev.loaded }),\n );\n options.onProgress?.(ev);\n },\n },\n requestId,\n );\n if (!tusResult.ok) {\n dispatch(\n updateUploadStatus({\n requestId,\n status: \"error\",\n error: tusResult.error,\n }),\n );\n return tusResult;\n }\n uploadData = {\n file_id: tusResult.fileId,\n file_path: tusResult.filePath,\n size_bytes: tusResult.fileSize,\n checksum: null,\n version_number: tusResult.versionNumber,\n url: tusResult.url,\n };\n } else {\n const params = {\n file,\n filePath,\n visibility: options.visibility ?? \"personal\",\n shareWith: options.shareWith,\n shareLevel: options.shareLevel,\n changeSummary: options.changeSummary,\n // Shared envelope builder — identical object to the TUS transport's\n // `metadata_json` (parity is unit-tested).\n metadata: buildUploadMetadataEnvelope(options.metadata),\n };\n\n const upload = await Files_uploadFileWithProgress(\n params,\n (ev) => {\n dispatch(\n updateUploadProgress({\n requestId,\n bytesUploaded: ev.loaded,\n }),\n );\n options.onProgress?.(ev);\n },\n { requestId, signal: options.signal, idempotencyKey: requestId },\n );\n uploadData = {\n file_id: upload.data.file_id,\n file_path: upload.data.file_path,\n size_bytes: upload.data.size_bytes,\n checksum: upload.data.checksum ?? null,\n version_number: upload.data.version_number,\n url: upload.data.url ?? null,\n };\n }\n\n // 2. Slice upsert — file is now visible in the tree without\n // waiting for the realtime echo or a refetch.\n dispatch(\n upsertFile(\n apiFileRecordToCloudFile({\n id: uploadData.file_id,\n owner_id: \"\",\n file_path: uploadData.file_path,\n file_name: uploadData.file_path.split(\"/\").pop() ?? file.name,\n mime_type: file.type || null,\n // Phase 0 rename — see docs/PYTHON_UPDATES.md §3.\n size_bytes: uploadData.size_bytes,\n checksum: uploadData.checksum,\n visibility: options.visibility ?? \"personal\",\n current_version: uploadData.version_number,\n parent_folder_id: options.parentFolderId ?? null,\n metadata: options.metadata ?? {},\n created_at: null,\n updated_at: null,\n deleted_at: null,\n }),\n ),\n );\n if (options.parentFolderId) {\n dispatch(\n attachChildToFolder({\n parentFolderId: options.parentFolderId,\n kind: \"file\",\n id: uploadData.file_id,\n }),\n );\n }\n dispatch(\n updateUploadStatus({\n requestId,\n status: \"success\",\n fileId: uploadData.file_id,\n }),\n );\n\n // 3. Optional share link.\n let shareToken: string | undefined;\n let shareUrl: string | undefined;\n let directUrl: string | undefined;\n if (options.createShareLink) {\n try {\n // Always emit the direct-file URL alongside the page URL.\n // Consumers default to `directUrl` for img/video/audio/iframe\n // src; `shareUrl` is for \"click here to view file metadata\".\n ({ shareToken, shareUrl, directUrl } = await mintUploadShareLink(\n uploadData.file_id,\n options,\n ));\n } catch (linkErr) {\n return {\n ok: false,\n error: `File uploaded but share link couldn't be created: ${extractErrorMessage(linkErr)}`,\n errorCode: \"share_link_failed\",\n fileName: file.name,\n };\n }\n }\n\n return {\n ok: true,\n fileId: uploadData.file_id,\n filePath: uploadData.file_path,\n // Phase 0 rename — see docs/PYTHON_UPDATES.md §3.\n fileSize: uploadData.size_bytes,\n versionNumber: uploadData.version_number,\n url: uploadData.url,\n shareToken,\n shareUrl,\n directUrl,\n };\n } catch (err) {\n const message = extractErrorMessage(err);\n dispatch(\n updateUploadStatus({\n requestId,\n status: \"error\",\n error: message,\n }),\n );\n return {\n ok: false,\n error: message,\n errorCode: (err as { code?: string } | null)?.code,\n fileName: file.name,\n };\n } finally {\n releaseRequest(requestId);\n }\n}\n\n// ─── Batch upload ────────────────────────────────────────────────────────\n\nexport interface CloudUploadManyOptions extends CloudUploadOptions {\n /** Parallel ceiling. Defaults to 3. */\n concurrency?: number;\n}\n\nexport interface CloudUploadManyResult {\n successes: CloudUploadSuccess[];\n failures: CloudUploadFailure[];\n}\n\n/**\n * Upload multiple files with bounded concurrency. Returns a structured\n * result so callers can show \"3 of 5 uploaded\" UI cleanly.\n */\nexport async function cloudUploadMany(\n files: File[],\n options: CloudUploadManyOptions,\n dispatch: AppDispatch,\n): Promise<CloudUploadManyResult> {\n const limit = Math.max(1, options.concurrency ?? 3);\n const successes: CloudUploadSuccess[] = [];\n const failures: CloudUploadFailure[] = [];\n const queue = [...files];\n\n async function worker(): Promise<void> {\n while (queue.length) {\n const file = queue.shift();\n if (!file) return;\n const result = await cloudUpload(file, options, dispatch);\n if (isCloudUploadSuccess(result)) {\n successes.push(result);\n } else {\n failures.push(result);\n }\n }\n }\n\n await Promise.all(\n Array.from({ length: Math.min(limit, files.length) }, worker),\n );\n\n return { successes, failures };\n}\n\n/**\n * Imperative shortcut for non-React code that still wants Redux side\n * effects. Pulls the dispatch from the store singleton.\n */\nexport async function cloudUploadImperative(\n file: File,\n options: CloudUploadOptions,\n): Promise<CloudUploadResult> {\n const { getStore } = await import(\"../host/store\");\n const store = getStore();\n if (!store) {\n return {\n ok: false,\n error: \"Redux store is not ready\",\n errorCode: \"store_not_ready\",\n fileName: file.name,\n };\n }\n return cloudUpload(file, options, store.dispatch as AppDispatch);\n}\n"],"mappings":"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","names":["resolveUploadTransport"]}
package/dist/files.cjs CHANGED
@@ -22,13 +22,13 @@ var files_exports = {};
22
22
  __export(files_exports, {
23
23
  CloudFolderDescriptions: () => CloudFolderDescriptions,
24
24
  CloudFolders: () => CloudFolders,
25
- ExternalFetchError: () => ExternalFetchError,
26
- FileAccessDeniedError: () => FileAccessDeniedError,
27
- FileDeletedError: () => FileDeletedError,
28
- FileHandlerError: () => FileHandlerError,
29
- FileNotFoundError: () => FileNotFoundError,
30
- FileUploadError: () => FileUploadError,
31
- ShareLinkInvalidError: () => ShareLinkInvalidError,
25
+ ExternalFetchError: () => import_files3.ExternalFetchError,
26
+ FileAccessDeniedError: () => import_files3.FileAccessDeniedError,
27
+ FileDeletedError: () => import_files3.FileDeletedError,
28
+ FileHandlerError: () => import_files3.FileError,
29
+ FileNotFoundError: () => import_files3.FileNotFoundError,
30
+ FileUploadError: () => import_files3.FileUploadError,
31
+ ShareLinkInvalidError: () => import_files3.ShareLinkInvalidError,
32
32
  UploadCancelledError: () => UploadCancelledError,
33
33
  VideoPublishDate: () => VideoPublishDate,
34
34
  blockFromMediaRef: () => blockFromMediaRef,
@@ -856,58 +856,9 @@ function VideoPublishDate({
856
856
  }
857
857
 
858
858
  // src/files/handler/errors.ts
859
- var FileHandlerError = class extends Error {
860
- code;
861
- fileId;
862
- cause;
863
- constructor(code, message, opts) {
864
- super(message);
865
- this.name = "FileHandlerError";
866
- this.code = code;
867
- this.fileId = opts?.fileId;
868
- this.cause = opts?.cause;
869
- }
870
- };
871
- var FileAccessDeniedError = class extends FileHandlerError {
872
- constructor(message = "This file isn't available to you.", opts) {
873
- super("access_denied", message, opts);
874
- this.name = "FileAccessDeniedError";
875
- }
876
- };
877
- var FileNotFoundError = class extends FileHandlerError {
878
- // A 404 from the file server. It proves the server could not retrieve the
879
- // file — NOT that the file was deleted (that is `FileDeletedError`, which
880
- // has a `deleted_at` to point at) and not that it never existed.
881
- constructor(message = "We couldn't retrieve this file.", opts) {
882
- super("not_found", message, opts);
883
- this.name = "FileNotFoundError";
884
- }
885
- };
886
- var FileDeletedError = class extends FileHandlerError {
887
- constructor(message = "File is in trash", opts) {
888
- super("deleted", message, opts);
889
- this.name = "FileDeletedError";
890
- }
891
- };
892
- var ShareLinkInvalidError = class extends FileHandlerError {
893
- constructor(message = "Share link is invalid, expired, or revoked") {
894
- super("share_link_invalid", message);
895
- this.name = "ShareLinkInvalidError";
896
- }
897
- };
898
- var ExternalFetchError = class extends FileHandlerError {
899
- constructor(message = "Failed to fetch external URL", opts) {
900
- super("external_fetch_failed", message, opts);
901
- this.name = "ExternalFetchError";
902
- }
903
- };
904
- var FileUploadError = class extends FileHandlerError {
905
- constructor(message, opts) {
906
- super("upload_failed", message, opts);
907
- this.name = "FileUploadError";
908
- }
909
- };
910
- var UploadCancelledError = class extends FileHandlerError {
859
+ var import_files2 = require("@ai-matrx/data/files");
860
+ var import_files3 = require("@ai-matrx/data/files");
861
+ var UploadCancelledError = class extends import_files2.FileError {
911
862
  constructor(message = "Upload cancelled \u2014 no workspace was selected.") {
912
863
  super("upload_cancelled", message);
913
864
  this.name = "UploadCancelledError";