@ai-matrx/media 0.8.0 → 0.9.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.
Files changed (185) hide show
  1. package/CHANGELOG.md +21 -0
  2. package/dist/files/engine/api/assets.d.ts +223 -0
  3. package/dist/files/engine/api/assets.js +124 -0
  4. package/dist/files/engine/api/assets.js.map +1 -0
  5. package/dist/files/engine/api/direct.d.ts +43 -0
  6. package/dist/files/engine/api/direct.js +73 -0
  7. package/dist/files/engine/api/direct.js.map +1 -0
  8. package/dist/files/engine/api/fileOrganization.d.ts +34 -0
  9. package/dist/files/engine/api/fileOrganization.js +38 -0
  10. package/dist/files/engine/api/fileOrganization.js.map +1 -0
  11. package/dist/files/engine/api/files.d.ts +249 -0
  12. package/dist/files/engine/api/files.js +231 -0
  13. package/dist/files/engine/api/files.js.map +1 -0
  14. package/dist/files/engine/api/folders.d.ts +63 -0
  15. package/dist/files/engine/api/folders.js +56 -0
  16. package/dist/files/engine/api/folders.js.map +1 -0
  17. package/dist/files/engine/api/office.d.ts +27 -0
  18. package/dist/files/engine/api/office.js +19 -0
  19. package/dist/files/engine/api/office.js.map +1 -0
  20. package/dist/files/engine/api/permissions.d.ts +37 -0
  21. package/dist/files/engine/api/permissions.js +109 -0
  22. package/dist/files/engine/api/permissions.js.map +1 -0
  23. package/dist/files/engine/api/versions.d.ts +26 -0
  24. package/dist/files/engine/api/versions.js +37 -0
  25. package/dist/files/engine/api/versions.js.map +1 -0
  26. package/dist/files/engine/cache/idb-store.d.ts +95 -0
  27. package/dist/files/engine/cache/idb-store.js +172 -0
  28. package/dist/files/engine/cache/idb-store.js.map +1 -0
  29. package/dist/files/engine/cache/policy.d.ts +14 -0
  30. package/dist/files/engine/cache/policy.js +40 -0
  31. package/dist/files/engine/cache/policy.js.map +1 -0
  32. package/dist/files/engine/cache/register-service-worker.d.ts +57 -0
  33. package/dist/files/engine/cache/register-service-worker.js +80 -0
  34. package/dist/files/engine/cache/register-service-worker.js.map +1 -0
  35. package/dist/files/engine/db-types.d.ts +48865 -0
  36. package/dist/files/engine/db-types.js +1 -0
  37. package/dist/files/engine/db-types.js.map +1 -0
  38. package/dist/files/engine/filesDb.d.ts +1913 -0
  39. package/dist/files/engine/filesDb.js +28 -0
  40. package/dist/files/engine/filesDb.js.map +1 -0
  41. package/dist/files/engine/handler/handler.d.ts +82 -0
  42. package/dist/files/engine/handler/handler.js +124 -0
  43. package/dist/files/engine/handler/handler.js.map +1 -0
  44. package/dist/files/engine/handler/hooks/useFile.d.ts +17 -0
  45. package/dist/files/engine/handler/hooks/useFile.js +75 -0
  46. package/dist/files/engine/handler/hooks/useFile.js.map +1 -0
  47. package/dist/files/engine/handler/hooks/useFileUpload.d.ts +76 -0
  48. package/dist/files/engine/handler/hooks/useFileUpload.js +67 -0
  49. package/dist/files/engine/handler/hooks/useFileUpload.js.map +1 -0
  50. package/dist/files/engine/handler/input/normalize.d.ts +14 -0
  51. package/dist/files/engine/handler/input/normalize.js +365 -0
  52. package/dist/files/engine/handler/input/normalize.js.map +1 -0
  53. package/dist/files/engine/handler/intelligence/access.d.ts +35 -0
  54. package/dist/files/engine/handler/intelligence/access.js +85 -0
  55. package/dist/files/engine/handler/intelligence/access.js.map +1 -0
  56. package/dist/files/engine/handler/intelligence/magic-bytes.d.ts +21 -0
  57. package/dist/files/engine/handler/intelligence/magic-bytes.js +67 -0
  58. package/dist/files/engine/handler/intelligence/magic-bytes.js.map +1 -0
  59. package/dist/files/engine/handler/output/target.d.ts +19 -0
  60. package/dist/files/engine/handler/output/target.js +222 -0
  61. package/dist/files/engine/handler/output/target.js.map +1 -0
  62. package/dist/files/engine/handler/resolver.d.ts +34 -0
  63. package/dist/files/engine/handler/resolver.js +133 -0
  64. package/dist/files/engine/handler/resolver.js.map +1 -0
  65. package/dist/files/engine/handler/types.d.ts +367 -0
  66. package/dist/files/engine/handler/types.js +1 -0
  67. package/dist/files/engine/handler/types.js.map +1 -0
  68. package/dist/files/engine/handler/upload.d.ts +32 -0
  69. package/dist/files/engine/handler/upload.js +295 -0
  70. package/dist/files/engine/handler/upload.js.map +1 -0
  71. package/dist/files/engine/handler/utils/classify.d.ts +14 -0
  72. package/dist/files/engine/handler/utils/classify.js +27 -0
  73. package/dist/files/engine/handler/utils/classify.js.map +1 -0
  74. package/dist/files/engine/handler/utils/prefer-locator.d.ts +32 -0
  75. package/dist/files/engine/handler/utils/prefer-locator.js +34 -0
  76. package/dist/files/engine/handler/utils/prefer-locator.js.map +1 -0
  77. package/dist/files/engine/handler/utils/python-base.d.ts +129 -0
  78. package/dist/files/engine/handler/utils/python-base.js +60 -0
  79. package/dist/files/engine/handler/utils/python-base.js.map +1 -0
  80. package/dist/files/engine/hooks/blob-cache.d.ts +130 -0
  81. package/dist/files/engine/hooks/blob-cache.js +162 -0
  82. package/dist/files/engine/hooks/blob-cache.js.map +1 -0
  83. package/dist/files/engine/hooks/office-extraction-cache.d.ts +28 -0
  84. package/dist/files/engine/hooks/office-extraction-cache.js +67 -0
  85. package/dist/files/engine/hooks/office-extraction-cache.js.map +1 -0
  86. package/dist/files/engine/host/configure.d.ts +166 -0
  87. package/dist/files/engine/host/configure.js +31 -0
  88. package/dist/files/engine/host/configure.js.map +1 -0
  89. package/dist/files/engine/host/org.d.ts +8 -0
  90. package/dist/files/engine/host/org.js +16 -0
  91. package/dist/files/engine/host/org.js.map +1 -0
  92. package/dist/files/engine/host/python-client.d.ts +60 -0
  93. package/dist/files/engine/host/python-client.js +46 -0
  94. package/dist/files/engine/host/python-client.js.map +1 -0
  95. package/dist/files/engine/host/share-links.d.ts +20 -0
  96. package/dist/files/engine/host/share-links.js +32 -0
  97. package/dist/files/engine/host/share-links.js.map +1 -0
  98. package/dist/files/engine/host/store.d.ts +24 -0
  99. package/dist/files/engine/host/store.js +23 -0
  100. package/dist/files/engine/host/store.js.map +1 -0
  101. package/dist/files/engine/host/supabase.d.ts +3808 -0
  102. package/dist/files/engine/host/supabase.js +33 -0
  103. package/dist/files/engine/host/supabase.js.map +1 -0
  104. package/dist/files/engine/host/toast.d.ts +14 -0
  105. package/dist/files/engine/host/toast.js +20 -0
  106. package/dist/files/engine/host/toast.js.map +1 -0
  107. package/dist/files/engine/host/typed-client.d.ts +136 -0
  108. package/dist/files/engine/host/typed-client.js +66 -0
  109. package/dist/files/engine/host/typed-client.js.map +1 -0
  110. package/dist/files/engine/index.d.ts +22 -0
  111. package/dist/files/engine/index.js +49 -0
  112. package/dist/files/engine/index.js.map +1 -0
  113. package/dist/files/engine/media/our-file-sources.d.ts +39 -0
  114. package/dist/files/engine/media/our-file-sources.js +83 -0
  115. package/dist/files/engine/media/our-file-sources.js.map +1 -0
  116. package/dist/files/engine/media/signed-url.d.ts +1 -0
  117. package/dist/files/engine/media/signed-url.js +6 -0
  118. package/dist/files/engine/media/signed-url.js.map +1 -0
  119. package/dist/files/engine/redux/converters.d.ts +73 -0
  120. package/dist/files/engine/redux/converters.js +286 -0
  121. package/dist/files/engine/redux/converters.js.map +1 -0
  122. package/dist/files/engine/redux/file-hydration.d.ts +15 -0
  123. package/dist/files/engine/redux/file-hydration.js +46 -0
  124. package/dist/files/engine/redux/file-hydration.js.map +1 -0
  125. package/dist/files/engine/redux/file-tree-auth-boundary.d.ts +5 -0
  126. package/dist/files/engine/redux/file-tree-auth-boundary.js +28 -0
  127. package/dist/files/engine/redux/file-tree-auth-boundary.js.map +1 -0
  128. package/dist/files/engine/redux/file-tree-timeout.d.ts +32 -0
  129. package/dist/files/engine/redux/file-tree-timeout.js +73 -0
  130. package/dist/files/engine/redux/file-tree-timeout.js.map +1 -0
  131. package/dist/files/engine/redux/mutation-toast-middleware.d.ts +27 -0
  132. package/dist/files/engine/redux/mutation-toast-middleware.js +103 -0
  133. package/dist/files/engine/redux/mutation-toast-middleware.js.map +1 -0
  134. package/dist/files/engine/redux/realtime-middleware.d.ts +61 -0
  135. package/dist/files/engine/redux/realtime-middleware.js +431 -0
  136. package/dist/files/engine/redux/realtime-middleware.js.map +1 -0
  137. package/dist/files/engine/redux/request-ledger.d.ts +68 -0
  138. package/dist/files/engine/redux/request-ledger.js +66 -0
  139. package/dist/files/engine/redux/request-ledger.js.map +1 -0
  140. package/dist/files/engine/redux/selectors.d.ts +3254 -0
  141. package/dist/files/engine/redux/selectors.js +428 -0
  142. package/dist/files/engine/redux/selectors.js.map +1 -0
  143. package/dist/files/engine/redux/slice.d.ts +157 -0
  144. package/dist/files/engine/redux/slice.js +836 -0
  145. package/dist/files/engine/redux/slice.js.map +1 -0
  146. package/dist/files/engine/redux/thunks.d.ts +449 -0
  147. package/dist/files/engine/redux/thunks.js +1684 -0
  148. package/dist/files/engine/redux/thunks.js.map +1 -0
  149. package/dist/files/engine/redux/tree-utils.d.ts +90 -0
  150. package/dist/files/engine/redux/tree-utils.js +200 -0
  151. package/dist/files/engine/redux/tree-utils.js.map +1 -0
  152. package/dist/files/engine/support/claimsUser.d.ts +77 -0
  153. package/dist/files/engine/support/claimsUser.js +48 -0
  154. package/dist/files/engine/support/claimsUser.js.map +1 -0
  155. package/dist/files/engine/support/datetime.d.ts +6 -0
  156. package/dist/files/engine/support/datetime.js +15 -0
  157. package/dist/files/engine/support/datetime.js.map +1 -0
  158. package/dist/files/engine/support/document-visibility.d.ts +14 -0
  159. package/dist/files/engine/support/document-visibility.js +28 -0
  160. package/dist/files/engine/support/document-visibility.js.map +1 -0
  161. package/dist/files/engine/support/logger.d.ts +28 -0
  162. package/dist/files/engine/support/logger.js +36 -0
  163. package/dist/files/engine/support/logger.js.map +1 -0
  164. package/dist/files/engine/types.d.ts +1081 -0
  165. package/dist/files/engine/types.js +30 -0
  166. package/dist/files/engine/types.js.map +1 -0
  167. package/dist/files/engine/upload/cloudUpload.d.ts +182 -0
  168. package/dist/files/engine/upload/cloudUpload.js +418 -0
  169. package/dist/files/engine/upload/cloudUpload.js.map +1 -0
  170. package/dist/files/engine/upload/tusUpload.d.ts +100 -0
  171. package/dist/files/engine/upload/tusUpload.js +258 -0
  172. package/dist/files/engine/upload/tusUpload.js.map +1 -0
  173. package/dist/files/engine/upload/uploadDedupGuard.d.ts +58 -0
  174. package/dist/files/engine/upload/uploadDedupGuard.js +41 -0
  175. package/dist/files/engine/upload/uploadDedupGuard.js.map +1 -0
  176. package/dist/files/engine/upload/uploadGuardOpeners.d.ts +70 -0
  177. package/dist/files/engine/upload/uploadGuardOpeners.js +54 -0
  178. package/dist/files/engine/upload/uploadGuardOpeners.js.map +1 -0
  179. package/dist/files/engine/utils/file-types.d.ts +243 -0
  180. package/dist/files/engine/utils/file-types.js +1722 -0
  181. package/dist/files/engine/utils/file-types.js.map +1 -0
  182. package/dist/files/engine/utils/user-visible.d.ts +133 -0
  183. package/dist/files/engine/utils/user-visible.js +115 -0
  184. package/dist/files/engine/utils/user-visible.js.map +1 -0
  185. package/package.json +392 -8
@@ -0,0 +1,28 @@
1
+ function filesDb(client) {
2
+ return client.schema("files");
3
+ }
4
+ const FILES_TABLE_COLUMNS = "id, created_by, updated_by, version, file_path, file_name, mime_type, size_bytes, checksum, visibility, current_version, parent_folder_id, metadata, created_at, updated_at, deleted_at, organization_id, parent_file_id, derivation_kind, derivation_metadata, duplicate_of_file_id, canonical_processed_document_id, width, height, duration_ms, origin_device_id, artifact_kind";
5
+ const FILE_VERSIONS_TABLE_COLUMNS = "id, file_id, version_number, size_bytes, checksum, created_by, created_at, change_summary, organization_id, metadata";
6
+ async function readFileRowById(client, fileId) {
7
+ const { data, error } = await filesDb(client).from("files").select(FILES_TABLE_COLUMNS).eq("id", fileId).is("deleted_at", null).maybeSingle();
8
+ if (error) throw error;
9
+ return data;
10
+ }
11
+ async function readFileSizesByIds(client, fileIds) {
12
+ const out = /* @__PURE__ */ new Map();
13
+ if (fileIds.length === 0) return out;
14
+ const { data, error } = await filesDb(client).from("files").select("id, size_bytes").in("id", fileIds).is("deleted_at", null);
15
+ if (error) throw error;
16
+ for (const row of data ?? []) {
17
+ out.set(row.id, row.size_bytes ?? null);
18
+ }
19
+ return out;
20
+ }
21
+ export {
22
+ FILES_TABLE_COLUMNS,
23
+ FILE_VERSIONS_TABLE_COLUMNS,
24
+ filesDb,
25
+ readFileRowById,
26
+ readFileSizesByIds
27
+ };
28
+ //# sourceMappingURL=filesDb.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../../src/files/engine/filesDb.ts"],"sourcesContent":["/**\n * features/files/filesDb.ts\n *\n * The cloud-files tables live in the dedicated `files` Postgres schema (moved\n * out of `public` in the 2026 DB restructure; the `cld_` prefix was dropped).\n * supabase-js reaches a non-public schema via `.schema(...)`, so every table\n * read/write for these tables must go through `client.schema('files')`.\n *\n * Use this helper instead of inlining `.schema('files')` everywhere:\n *\n * const db = filesDb(supabase);\n * const { data } = await db.from('files').select(FILES_TABLE_COLUMNS);\n * await db.from('folders').upsert(...); // files.folders\n *\n * NEVER `select('*')` on `files.files` / `files.file_versions` — the\n * server-only `storage_uri` column grant is REVOKEd and `*` errors.\n *\n * Works with the browser client, the SSR server client, and the admin client —\n * they all expose `.schema()`.\n *\n * NOTE: file-permission grants are NOT in this schema — they live in the\n * canonical `iam.permissions` store (resource_type='file'/'folder'). Reach\n * those via `iamDb(supabase).rpc('fn_list_resource_permissions', ...)` (see\n * utils/supabase/iamDb.ts) — list/grant/revoke are admin-gated inside the\n * RPC (iam.has_access), never a plain `.from('permissions')` select.\n */\nimport type { SupabaseClient } from \"@supabase/supabase-js\";\nimport type { Database } from \"./db-types\";\n\n/** A supabase client scoped to the `files` schema. */\nexport function filesDb<C extends SupabaseClient<Database>>(client: C) {\n return client.schema(\"files\");\n}\n\n/**\n * The ONLY legal column list for reading `files.files` from the client.\n *\n * `storage_uri` (the native S3 location) is server-only: the column grant is\n * REVOKEd from `authenticated`, so `select(\"*\")` on `files.files` ERRORS with\n * \"permission denied for column storage_uri\". Never select `*` on this table\n * and never add `storage_uri` here. Renderable URLs come from the server's\n * FileRecord/FileRef URL contract (`url` / `cdn_url` / `download_url`),\n * never assembled client-side from a storage location.\n */\nexport const FILES_TABLE_COLUMNS =\n \"id, created_by, updated_by, version, file_path, file_name, mime_type, size_bytes, checksum, visibility, current_version, parent_folder_id, metadata, created_at, updated_at, deleted_at, organization_id, parent_file_id, derivation_kind, derivation_metadata, duplicate_of_file_id, canonical_processed_document_id, width, height, duration_ms, origin_device_id, artifact_kind\";\n\n/**\n * Same rule for `files.file_versions` — its `storage_uri` is server-only.\n * Version bytes download via the server (`/files/{id}/versions/{n}/download`).\n */\nexport const FILE_VERSIONS_TABLE_COLUMNS =\n \"id, file_id, version_number, size_bytes, checksum, created_by, created_at, change_summary, organization_id, metadata\";\n\n/**\n * Read one complete, RLS-authorized file row by durable identity.\n *\n * REST `FileRecord` is a render/navigation envelope and historically omitted\n * the owning organization. Mutation preparation must use this canonical DB\n * row so a path-targeted version write can preserve ownership exactly.\n */\nexport async function readFileRowById<C extends SupabaseClient<Database>>(\n client: C,\n fileId: string,\n) {\n const { data, error } = await filesDb(client)\n .from(\"files\")\n .select(FILES_TABLE_COLUMNS)\n .eq(\"id\", fileId)\n .is(\"deleted_at\", null)\n .maybeSingle();\n if (error) throw error;\n return data;\n}\n\n/**\n * Batch-read just the byte size for a set of file ids, RLS-authorized like\n * every other read here.\n *\n * Built for a source list that names files by id (a Rulebook's attached\n * sources, a resource pile) and needs to show — before any run touches\n * them — which of those files are genuinely empty. `size_bytes` alone (no\n * `storage_uri`, no need for the single-row reader's full column list) keeps\n * this cheap enough to call for a whole pile at once.\n */\nexport async function readFileSizesByIds<C extends SupabaseClient<Database>>(\n client: C,\n fileIds: string[],\n): Promise<Map<string, number | null>> {\n const out = new Map<string, number | null>();\n if (fileIds.length === 0) return out;\n const { data, error } = await filesDb(client)\n .from(\"files\")\n .select(\"id, size_bytes\")\n .in(\"id\", fileIds)\n .is(\"deleted_at\", null);\n if (error) throw error;\n for (const row of data ?? []) {\n out.set(row.id, row.size_bytes ?? null);\n }\n return out;\n}\n"],"mappings":"AA8BO,SAAS,QAA4C,QAAW;AACrE,SAAO,OAAO,OAAO,OAAO;AAC9B;AAYO,MAAM,sBACX;AAMK,MAAM,8BACX;AASF,eAAsB,gBACpB,QACA,QACA;AACA,QAAM,EAAE,MAAM,MAAM,IAAI,MAAM,QAAQ,MAAM,EACzC,KAAK,OAAO,EACZ,OAAO,mBAAmB,EAC1B,GAAG,MAAM,MAAM,EACf,GAAG,cAAc,IAAI,EACrB,YAAY;AACf,MAAI,MAAO,OAAM;AACjB,SAAO;AACT;AAYA,eAAsB,mBACpB,QACA,SACqC;AACrC,QAAM,MAAM,oBAAI,IAA2B;AAC3C,MAAI,QAAQ,WAAW,EAAG,QAAO;AACjC,QAAM,EAAE,MAAM,MAAM,IAAI,MAAM,QAAQ,MAAM,EACzC,KAAK,OAAO,EACZ,OAAO,gBAAgB,EACvB,GAAG,MAAM,OAAO,EAChB,GAAG,cAAc,IAAI;AACxB,MAAI,MAAO,OAAM;AACjB,aAAW,OAAO,QAAQ,CAAC,GAAG;AAC5B,QAAI,IAAI,IAAI,IAAI,IAAI,cAAc,IAAI;AAAA,EACxC;AACA,SAAO;AACT;","names":[]}
@@ -0,0 +1,82 @@
1
+ /**
2
+ * features/files/handler/handler.ts
3
+ *
4
+ * The single public entry point. Every callsite that touches a file —
5
+ * components, thunks, server routes, agent prep — uses one of these
6
+ * three primitives. Direct construction of media blocks, direct calls
7
+ * to direct object-store SDKs, and uploads through `useFileUpload` are
8
+ * all banned (see `.eslintrc`).
9
+ *
10
+ * fileHandler.use(source).as(target) — read path
11
+ * fileHandler.upload(source, opts) — write path
12
+ * fileHandler.refresh(file) — repair path
13
+ */
14
+ import type { EnsureFolderPathArg } from "../types.js";
15
+ import type { FileSource, FileTarget, NormalizedFile, RenderedFor, UploadedNormalizedFile, UploadOpts } from "./types.js";
16
+ declare class HandlerHandle {
17
+ private file;
18
+ constructor(file: NormalizedFile);
19
+ as<T extends FileTarget>(target: T): Promise<RenderedFor<T>>;
20
+ resolve(): Promise<NormalizedFile>;
21
+ }
22
+ export declare const fileHandler: {
23
+ /**
24
+ * Start a chain. The chain is sync up until you call `as(...)`, which
25
+ * resolves and renders for the chosen target.
26
+ *
27
+ * const url = await fileHandler.use({ kind: "file_id", fileId }).as({ kind: "html_src" });
28
+ */
29
+ use(source: FileSource): HandlerHandle;
30
+ /**
31
+ * One-shot resolve when you don't have a target yet (e.g. you want to
32
+ * inspect capabilities before deciding what to do).
33
+ */
34
+ resolve(source: FileSource): Promise<NormalizedFile>;
35
+ /**
36
+ * One-shot upload. Resolves to a fully-hydrated NormalizedFile pointing
37
+ * at the freshly-created cld_files row.
38
+ */
39
+ upload(source: FileSource, opts?: UploadOpts): Promise<UploadedNormalizedFile>;
40
+ /**
41
+ * Re-resolve a file after a DB event (visibility change, share
42
+ * revocation) so origin/capabilities and the bound URL are current.
43
+ */
44
+ refresh(file: NormalizedFile): Promise<NormalizedFile>;
45
+ /**
46
+ * Convenience: skip straight to a media block (the most common use).
47
+ */
48
+ toMediaBlock(source: FileSource): Promise<import("./types.js").MediaBlock>;
49
+ /**
50
+ * Convenience: skip straight to a MediaRef (for outbound APIs that
51
+ * already accept MediaRef directly).
52
+ */
53
+ toMediaRef(source: FileSource): Promise<import("../types.js").MediaRef>;
54
+ /**
55
+ * Convenience: skip straight to a persisted JSONB content part for
56
+ * `cx_message.content[]`.
57
+ */
58
+ toContentPart(source: FileSource): Promise<import("@ai-matrx/agents/generated/stream-events").MessagePart>;
59
+ /**
60
+ * Imperatively move a file to Trash (soft delete — restorable; there is
61
+ * no hard delete). Routes through the
62
+ * `deleteFile` thunk against the store singleton so the slice and
63
+ * realtime channel stay in sync — identical to `useFileMutation().remove()`.
64
+ *
65
+ * For service-layer and non-React callers (e.g. cleanup after a failed
66
+ * upload). React components should prefer `useFileMutation` so they get
67
+ * hook-lifecycle guarantees.
68
+ */
69
+ remove(fileId: string): Promise<void>;
70
+ /**
71
+ * Idempotently create (or look up) a folder path like `"Images/Crops"`,
72
+ * walking each segment and creating any that are missing. Returns the
73
+ * leaf folder id.
74
+ *
75
+ * Non-React entry point — for services and thunks that need to stage
76
+ * an upload destination without owning a React hook. Routes through
77
+ * the `ensureFolderPath` thunk against the store singleton, so realtime
78
+ * channel updates land in the slice the same way.
79
+ */
80
+ ensureFolderPath(arg: EnsureFolderPathArg): Promise<string>;
81
+ };
82
+ export type { FileSource, FileTarget, NormalizedFile, UploadedNormalizedFile, UploadOpts, } from "./types.js";
@@ -0,0 +1,124 @@
1
+ import { uploadInternal } from "./upload.js";
2
+ import { normalize } from "./input/normalize.js";
3
+ import { resolve as resolveImpl } from "./resolver.js";
4
+ import { toTarget } from "./output/target.js";
5
+ import { getStoreSingleton } from "../host/store.js";
6
+ import {
7
+ deleteFile as deleteFileThunk,
8
+ ensureFolderPath as ensureFolderPathThunk
9
+ } from "../redux/thunks.js";
10
+ class HandlerHandle {
11
+ constructor(file) {
12
+ this.file = file;
13
+ }
14
+ file;
15
+ async as(target) {
16
+ const resolved = await resolveImpl(this.file, {
17
+ needsUrl: targetNeedsUrl(target.kind),
18
+ sniffMime: target.kind === "media_block"
19
+ });
20
+ return toTarget(resolved, target);
21
+ }
22
+ async resolve() {
23
+ return resolveImpl(this.file);
24
+ }
25
+ }
26
+ function targetNeedsUrl(kind) {
27
+ return kind !== "media_block" && kind !== "media_ref";
28
+ }
29
+ const fileHandler = {
30
+ /**
31
+ * Start a chain. The chain is sync up until you call `as(...)`, which
32
+ * resolves and renders for the chosen target.
33
+ *
34
+ * const url = await fileHandler.use({ kind: "file_id", fileId }).as({ kind: "html_src" });
35
+ */
36
+ use(source) {
37
+ return new HandlerHandle(normalize(source));
38
+ },
39
+ /**
40
+ * One-shot resolve when you don't have a target yet (e.g. you want to
41
+ * inspect capabilities before deciding what to do).
42
+ */
43
+ async resolve(source) {
44
+ return resolveImpl(normalize(source));
45
+ },
46
+ /**
47
+ * One-shot upload. Resolves to a fully-hydrated NormalizedFile pointing
48
+ * at the freshly-created cld_files row.
49
+ */
50
+ async upload(source, opts = {}) {
51
+ return uploadInternal(source, opts);
52
+ },
53
+ /**
54
+ * Re-resolve a file after a DB event (visibility change, share
55
+ * revocation) so origin/capabilities and the bound URL are current.
56
+ */
57
+ async refresh(file) {
58
+ return resolveImpl(file, { needsUrl: true });
59
+ },
60
+ /**
61
+ * Convenience: skip straight to a media block (the most common use).
62
+ */
63
+ async toMediaBlock(source) {
64
+ return this.use(source).as({ kind: "media_block" });
65
+ },
66
+ /**
67
+ * Convenience: skip straight to a MediaRef (for outbound APIs that
68
+ * already accept MediaRef directly).
69
+ */
70
+ async toMediaRef(source) {
71
+ return this.use(source).as({ kind: "media_ref" });
72
+ },
73
+ /**
74
+ * Convenience: skip straight to a persisted JSONB content part for
75
+ * `cx_message.content[]`.
76
+ */
77
+ async toContentPart(source) {
78
+ return this.use(source).as({ kind: "jsonb_content_part" });
79
+ },
80
+ /**
81
+ * Imperatively move a file to Trash (soft delete — restorable; there is
82
+ * no hard delete). Routes through the
83
+ * `deleteFile` thunk against the store singleton so the slice and
84
+ * realtime channel stay in sync — identical to `useFileMutation().remove()`.
85
+ *
86
+ * For service-layer and non-React callers (e.g. cleanup after a failed
87
+ * upload). React components should prefer `useFileMutation` so they get
88
+ * hook-lifecycle guarantees.
89
+ */
90
+ async remove(fileId) {
91
+ const store = getStoreSingleton();
92
+ if (!store) {
93
+ throw new Error(
94
+ "fileHandler.remove called before the Redux store was ready."
95
+ );
96
+ }
97
+ const dispatch = store.dispatch;
98
+ await dispatch(deleteFileThunk({ fileId })).unwrap();
99
+ },
100
+ /**
101
+ * Idempotently create (or look up) a folder path like `"Images/Crops"`,
102
+ * walking each segment and creating any that are missing. Returns the
103
+ * leaf folder id.
104
+ *
105
+ * Non-React entry point — for services and thunks that need to stage
106
+ * an upload destination without owning a React hook. Routes through
107
+ * the `ensureFolderPath` thunk against the store singleton, so realtime
108
+ * channel updates land in the slice the same way.
109
+ */
110
+ async ensureFolderPath(arg) {
111
+ const store = getStoreSingleton();
112
+ if (!store) {
113
+ throw new Error(
114
+ "fileHandler.ensureFolderPath called before the Redux store was ready."
115
+ );
116
+ }
117
+ const dispatch = store.dispatch;
118
+ return await dispatch(ensureFolderPathThunk(arg)).unwrap();
119
+ }
120
+ };
121
+ export {
122
+ fileHandler
123
+ };
124
+ //# sourceMappingURL=handler.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../../../src/files/engine/handler/handler.ts"],"sourcesContent":["/**\n * features/files/handler/handler.ts\n *\n * The single public entry point. Every callsite that touches a file —\n * components, thunks, server routes, agent prep — uses one of these\n * three primitives. Direct construction of media blocks, direct calls\n * to direct object-store SDKs, and uploads through `useFileUpload` are\n * all banned (see `.eslintrc`).\n *\n * fileHandler.use(source).as(target) — read path\n * fileHandler.upload(source, opts) — write path\n * fileHandler.refresh(file) — repair path\n */\n\nimport { uploadInternal } from \"./upload\";\nimport { normalize } from \"./input/normalize\";\nimport { resolve as resolveImpl } from \"./resolver\";\nimport { toTarget } from \"./output/target\";\nimport { getStoreSingleton } from \"../host/store\";\nimport {\n deleteFile as deleteFileThunk,\n ensureFolderPath as ensureFolderPathThunk,\n} from \"../redux/thunks\";\nimport type { EnsureFolderPathArg } from \"../types\";\nimport type { AppDispatch } from \"../host/store\";\nimport type {\n FileSource,\n FileTarget,\n NormalizedFile,\n RenderedFor,\n UploadedNormalizedFile,\n UploadOpts,\n} from \"./types\";\n\nclass HandlerHandle {\n constructor(private file: NormalizedFile) {}\n\n async as<T extends FileTarget>(target: T): Promise<RenderedFor<T>> {\n const resolved = await resolveImpl(this.file, {\n needsUrl: targetNeedsUrl(target.kind),\n sniffMime: target.kind === \"media_block\",\n });\n return toTarget(resolved, target);\n }\n\n async resolve(): Promise<NormalizedFile> {\n return resolveImpl(this.file);\n }\n}\n\nfunction targetNeedsUrl(kind: FileTarget[\"kind\"]): boolean {\n return kind !== \"media_block\" && kind !== \"media_ref\";\n}\n\nexport const fileHandler = {\n /**\n * Start a chain. The chain is sync up until you call `as(...)`, which\n * resolves and renders for the chosen target.\n *\n * const url = await fileHandler.use({ kind: \"file_id\", fileId }).as({ kind: \"html_src\" });\n */\n use(source: FileSource): HandlerHandle {\n return new HandlerHandle(normalize(source));\n },\n\n /**\n * One-shot resolve when you don't have a target yet (e.g. you want to\n * inspect capabilities before deciding what to do).\n */\n async resolve(source: FileSource): Promise<NormalizedFile> {\n return resolveImpl(normalize(source));\n },\n\n /**\n * One-shot upload. Resolves to a fully-hydrated NormalizedFile pointing\n * at the freshly-created cld_files row.\n */\n async upload(\n source: FileSource,\n opts: UploadOpts = {},\n ): Promise<UploadedNormalizedFile> {\n return uploadInternal(source, opts);\n },\n\n /**\n * Re-resolve a file after a DB event (visibility change, share\n * revocation) so origin/capabilities and the bound URL are current.\n */\n async refresh(file: NormalizedFile): Promise<NormalizedFile> {\n return resolveImpl(file, { needsUrl: true });\n },\n\n /**\n * Convenience: skip straight to a media block (the most common use).\n */\n async toMediaBlock(source: FileSource) {\n return this.use(source).as({ kind: \"media_block\" });\n },\n\n /**\n * Convenience: skip straight to a MediaRef (for outbound APIs that\n * already accept MediaRef directly).\n */\n async toMediaRef(source: FileSource) {\n return this.use(source).as({ kind: \"media_ref\" });\n },\n\n /**\n * Convenience: skip straight to a persisted JSONB content part for\n * `cx_message.content[]`.\n */\n async toContentPart(source: FileSource) {\n return this.use(source).as({ kind: \"jsonb_content_part\" });\n },\n\n /**\n * Imperatively move a file to Trash (soft delete — restorable; there is\n * no hard delete). Routes through the\n * `deleteFile` thunk against the store singleton so the slice and\n * realtime channel stay in sync — identical to `useFileMutation().remove()`.\n *\n * For service-layer and non-React callers (e.g. cleanup after a failed\n * upload). React components should prefer `useFileMutation` so they get\n * hook-lifecycle guarantees.\n */\n async remove(fileId: string): Promise<void> {\n const store = getStoreSingleton();\n if (!store) {\n throw new Error(\n \"fileHandler.remove called before the Redux store was ready.\",\n );\n }\n const dispatch = store.dispatch as AppDispatch;\n await dispatch(deleteFileThunk({ fileId })).unwrap();\n },\n\n /**\n * Idempotently create (or look up) a folder path like `\"Images/Crops\"`,\n * walking each segment and creating any that are missing. Returns the\n * leaf folder id.\n *\n * Non-React entry point — for services and thunks that need to stage\n * an upload destination without owning a React hook. Routes through\n * the `ensureFolderPath` thunk against the store singleton, so realtime\n * channel updates land in the slice the same way.\n */\n async ensureFolderPath(arg: EnsureFolderPathArg): Promise<string> {\n const store = getStoreSingleton();\n if (!store) {\n throw new Error(\n \"fileHandler.ensureFolderPath called before the Redux store was ready.\",\n );\n }\n const dispatch = store.dispatch as AppDispatch;\n return await dispatch(ensureFolderPathThunk(arg)).unwrap();\n },\n};\n\nexport type {\n FileSource,\n FileTarget,\n NormalizedFile,\n UploadedNormalizedFile,\n UploadOpts,\n} from \"./types\";\n"],"mappings":"AAcA,SAAS,sBAAsB;AAC/B,SAAS,iBAAiB;AAC1B,SAAS,WAAW,mBAAmB;AACvC,SAAS,gBAAgB;AACzB,SAAS,yBAAyB;AAClC;AAAA,EACE,cAAc;AAAA,EACd,oBAAoB;AAAA,OACf;AAYP,MAAM,cAAc;AAAA,EAClB,YAAoB,MAAsB;AAAtB;AAAA,EAAuB;AAAA,EAAvB;AAAA,EAEpB,MAAM,GAAyB,QAAoC;AACjE,UAAM,WAAW,MAAM,YAAY,KAAK,MAAM;AAAA,MAC5C,UAAU,eAAe,OAAO,IAAI;AAAA,MACpC,WAAW,OAAO,SAAS;AAAA,IAC7B,CAAC;AACD,WAAO,SAAS,UAAU,MAAM;AAAA,EAClC;AAAA,EAEA,MAAM,UAAmC;AACvC,WAAO,YAAY,KAAK,IAAI;AAAA,EAC9B;AACF;AAEA,SAAS,eAAe,MAAmC;AACzD,SAAO,SAAS,iBAAiB,SAAS;AAC5C;AAEO,MAAM,cAAc;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAOzB,IAAI,QAAmC;AACrC,WAAO,IAAI,cAAc,UAAU,MAAM,CAAC;AAAA,EAC5C;AAAA;AAAA;AAAA;AAAA;AAAA,EAMA,MAAM,QAAQ,QAA6C;AACzD,WAAO,YAAY,UAAU,MAAM,CAAC;AAAA,EACtC;AAAA;AAAA;AAAA;AAAA;AAAA,EAMA,MAAM,OACJ,QACA,OAAmB,CAAC,GACa;AACjC,WAAO,eAAe,QAAQ,IAAI;AAAA,EACpC;AAAA;AAAA;AAAA;AAAA;AAAA,EAMA,MAAM,QAAQ,MAA+C;AAC3D,WAAO,YAAY,MAAM,EAAE,UAAU,KAAK,CAAC;AAAA,EAC7C;AAAA;AAAA;AAAA;AAAA,EAKA,MAAM,aAAa,QAAoB;AACrC,WAAO,KAAK,IAAI,MAAM,EAAE,GAAG,EAAE,MAAM,cAAc,CAAC;AAAA,EACpD;AAAA;AAAA;AAAA;AAAA;AAAA,EAMA,MAAM,WAAW,QAAoB;AACnC,WAAO,KAAK,IAAI,MAAM,EAAE,GAAG,EAAE,MAAM,YAAY,CAAC;AAAA,EAClD;AAAA;AAAA;AAAA;AAAA;AAAA,EAMA,MAAM,cAAc,QAAoB;AACtC,WAAO,KAAK,IAAI,MAAM,EAAE,GAAG,EAAE,MAAM,qBAAqB,CAAC;AAAA,EAC3D;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAYA,MAAM,OAAO,QAA+B;AAC1C,UAAM,QAAQ,kBAAkB;AAChC,QAAI,CAAC,OAAO;AACV,YAAM,IAAI;AAAA,QACR;AAAA,MACF;AAAA,IACF;AACA,UAAM,WAAW,MAAM;AACvB,UAAM,SAAS,gBAAgB,EAAE,OAAO,CAAC,CAAC,EAAE,OAAO;AAAA,EACrD;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAYA,MAAM,iBAAiB,KAA2C;AAChE,UAAM,QAAQ,kBAAkB;AAChC,QAAI,CAAC,OAAO;AACV,YAAM,IAAI;AAAA,QACR;AAAA,MACF;AAAA,IACF;AACA,UAAM,WAAW,MAAM;AACvB,WAAO,MAAM,SAAS,sBAAsB,GAAG,CAAC,EAAE,OAAO;AAAA,EAC3D;AACF;","names":[]}
@@ -0,0 +1,17 @@
1
+ /**
2
+ * features/files/handler/hooks/useFile.ts
3
+ *
4
+ * Resolve a `FileSource` to a `NormalizedFile`. Re-runs when the source's
5
+ * identity changes (its `kind` plus the relevant identifier — fileId, url,
6
+ * token, blob ref). The returned object is stable across renders that
7
+ * don't change those identifiers.
8
+ */
9
+ import type { FileSource, NormalizedFile } from "../types.js";
10
+ export type FileStatus = "idle" | "resolving" | "ready" | "error";
11
+ export interface UseFileResult {
12
+ file: NormalizedFile | null;
13
+ status: FileStatus;
14
+ error: Error | null;
15
+ reload: () => void;
16
+ }
17
+ export declare function useFile(source: FileSource | null | undefined): UseFileResult;
@@ -0,0 +1,75 @@
1
+ "use client";
2
+ import { useEffect, useState } from "react";
3
+ import { fileHandler } from "../handler.js";
4
+ function useFile(source) {
5
+ const [file, setFile] = useState(null);
6
+ const [status, setStatus] = useState("idle");
7
+ const [error, setError] = useState(null);
8
+ const [tick, setTick] = useState(0);
9
+ useEffect(() => {
10
+ if (!source) {
11
+ setFile(null);
12
+ setStatus("idle");
13
+ setError(null);
14
+ return void 0;
15
+ }
16
+ let cancelled = false;
17
+ setStatus("resolving");
18
+ setError(null);
19
+ fileHandler.resolve(source).then((resolved) => {
20
+ if (cancelled) return;
21
+ setFile(resolved);
22
+ setStatus("ready");
23
+ }).catch((err) => {
24
+ if (cancelled) return;
25
+ setError(err instanceof Error ? err : new Error(String(err)));
26
+ setStatus("error");
27
+ });
28
+ return () => {
29
+ cancelled = true;
30
+ };
31
+ }, [sourceKey(source), tick]);
32
+ return {
33
+ file,
34
+ status,
35
+ error,
36
+ reload: () => setTick((t) => t + 1)
37
+ };
38
+ }
39
+ function sourceKey(source) {
40
+ if (!source) return "<none>";
41
+ switch (source.kind) {
42
+ case "blob":
43
+ return `blob:${source.blob.size}:${source.fileName ?? ""}`;
44
+ case "file":
45
+ return `file:${source.file.name}:${source.file.size}:${source.file.lastModified}`;
46
+ case "buffer":
47
+ return `buffer:${source.fileName ?? ""}:${source.mime}`;
48
+ case "stream":
49
+ return `stream:${source.fileName ?? ""}`;
50
+ case "data_uri":
51
+ return `data:${source.dataUri.slice(0, 64)}`;
52
+ case "base64":
53
+ return `b64:${source.mime}:${source.base64.slice(0, 32)}`;
54
+ case "external_url":
55
+ return `ext:${source.url}`;
56
+ case "youtube":
57
+ return `yt:${source.url}`;
58
+ case "cloud_file":
59
+ return `cf:${source.cloudFile.id}`;
60
+ case "file_id":
61
+ return `id:${source.fileId}`;
62
+ case "share_link":
63
+ return `share:${source.token}`;
64
+ case "public_cdn":
65
+ return `cdn:${source.fileId ?? source.url}`;
66
+ case "upload_result":
67
+ return `up:${source.uploadResult.fileId ?? source.uploadResult.url}`;
68
+ case "stream_event":
69
+ return `ev:${JSON.stringify(source.payload).slice(0, 64)}`;
70
+ }
71
+ }
72
+ export {
73
+ useFile
74
+ };
75
+ //# sourceMappingURL=useFile.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../../../../src/files/engine/handler/hooks/useFile.ts"],"sourcesContent":["/**\n * features/files/handler/hooks/useFile.ts\n *\n * Resolve a `FileSource` to a `NormalizedFile`. Re-runs when the source's\n * identity changes (its `kind` plus the relevant identifier — fileId, url,\n * token, blob ref). The returned object is stable across renders that\n * don't change those identifiers.\n */\n\n\"use client\";\n\nimport { useEffect, useState } from \"react\";\nimport { fileHandler } from \"../handler\";\nimport type { FileSource, NormalizedFile } from \"../types\";\n\nexport type FileStatus = \"idle\" | \"resolving\" | \"ready\" | \"error\";\n\nexport interface UseFileResult {\n file: NormalizedFile | null;\n status: FileStatus;\n error: Error | null;\n reload: () => void;\n}\n\nexport function useFile(source: FileSource | null | undefined): UseFileResult {\n const [file, setFile] = useState<NormalizedFile | null>(null);\n const [status, setStatus] = useState<FileStatus>(\"idle\");\n const [error, setError] = useState<Error | null>(null);\n const [tick, setTick] = useState(0);\n\n useEffect(() => {\n if (!source) {\n setFile(null);\n setStatus(\"idle\");\n setError(null);\n return undefined;\n }\n\n let cancelled = false;\n setStatus(\"resolving\");\n setError(null);\n\n fileHandler\n .resolve(source)\n .then((resolved) => {\n if (cancelled) return;\n setFile(resolved);\n setStatus(\"ready\");\n })\n .catch((err) => {\n if (cancelled) return;\n setError(err instanceof Error ? err : new Error(String(err)));\n setStatus(\"error\");\n });\n\n return () => {\n cancelled = true;\n };\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, [sourceKey(source), tick]);\n\n return {\n file,\n status,\n error,\n reload: () => setTick((t) => t + 1),\n };\n}\n\n/**\n * Stable identity key for a FileSource. Different shapes have different\n * meaningful identifiers — this collapses each into one string so React\n * effects re-run only when the source actually changes.\n */\nfunction sourceKey(source: FileSource | null | undefined): string {\n if (!source) return \"<none>\";\n switch (source.kind) {\n case \"blob\":\n return `blob:${source.blob.size}:${source.fileName ?? \"\"}`;\n case \"file\":\n return `file:${source.file.name}:${source.file.size}:${source.file.lastModified}`;\n case \"buffer\":\n return `buffer:${source.fileName ?? \"\"}:${source.mime}`;\n case \"stream\":\n return `stream:${source.fileName ?? \"\"}`;\n case \"data_uri\":\n return `data:${source.dataUri.slice(0, 64)}`;\n case \"base64\":\n return `b64:${source.mime}:${source.base64.slice(0, 32)}`;\n case \"external_url\":\n return `ext:${source.url}`;\n case \"youtube\":\n return `yt:${source.url}`;\n case \"cloud_file\":\n return `cf:${source.cloudFile.id}`;\n case \"file_id\":\n return `id:${source.fileId}`;\n case \"share_link\":\n return `share:${source.token}`;\n case \"public_cdn\":\n return `cdn:${source.fileId ?? source.url}`;\n case \"upload_result\":\n return `up:${source.uploadResult.fileId ?? source.uploadResult.url}`;\n case \"stream_event\":\n return `ev:${JSON.stringify(source.payload).slice(0, 64)}`;\n }\n}\n"],"mappings":";AAWA,SAAS,WAAW,gBAAgB;AACpC,SAAS,mBAAmB;AAYrB,SAAS,QAAQ,QAAsD;AAC5E,QAAM,CAAC,MAAM,OAAO,IAAI,SAAgC,IAAI;AAC5D,QAAM,CAAC,QAAQ,SAAS,IAAI,SAAqB,MAAM;AACvD,QAAM,CAAC,OAAO,QAAQ,IAAI,SAAuB,IAAI;AACrD,QAAM,CAAC,MAAM,OAAO,IAAI,SAAS,CAAC;AAElC,YAAU,MAAM;AACd,QAAI,CAAC,QAAQ;AACX,cAAQ,IAAI;AACZ,gBAAU,MAAM;AAChB,eAAS,IAAI;AACb,aAAO;AAAA,IACT;AAEA,QAAI,YAAY;AAChB,cAAU,WAAW;AACrB,aAAS,IAAI;AAEb,gBACG,QAAQ,MAAM,EACd,KAAK,CAAC,aAAa;AAClB,UAAI,UAAW;AACf,cAAQ,QAAQ;AAChB,gBAAU,OAAO;AAAA,IACnB,CAAC,EACA,MAAM,CAAC,QAAQ;AACd,UAAI,UAAW;AACf,eAAS,eAAe,QAAQ,MAAM,IAAI,MAAM,OAAO,GAAG,CAAC,CAAC;AAC5D,gBAAU,OAAO;AAAA,IACnB,CAAC;AAEH,WAAO,MAAM;AACX,kBAAY;AAAA,IACd;AAAA,EAEF,GAAG,CAAC,UAAU,MAAM,GAAG,IAAI,CAAC;AAE5B,SAAO;AAAA,IACL;AAAA,IACA;AAAA,IACA;AAAA,IACA,QAAQ,MAAM,QAAQ,CAAC,MAAM,IAAI,CAAC;AAAA,EACpC;AACF;AAOA,SAAS,UAAU,QAA+C;AAChE,MAAI,CAAC,OAAQ,QAAO;AACpB,UAAQ,OAAO,MAAM;AAAA,IACnB,KAAK;AACH,aAAO,QAAQ,OAAO,KAAK,IAAI,IAAI,OAAO,YAAY,EAAE;AAAA,IAC1D,KAAK;AACH,aAAO,QAAQ,OAAO,KAAK,IAAI,IAAI,OAAO,KAAK,IAAI,IAAI,OAAO,KAAK,YAAY;AAAA,IACjF,KAAK;AACH,aAAO,UAAU,OAAO,YAAY,EAAE,IAAI,OAAO,IAAI;AAAA,IACvD,KAAK;AACH,aAAO,UAAU,OAAO,YAAY,EAAE;AAAA,IACxC,KAAK;AACH,aAAO,QAAQ,OAAO,QAAQ,MAAM,GAAG,EAAE,CAAC;AAAA,IAC5C,KAAK;AACH,aAAO,OAAO,OAAO,IAAI,IAAI,OAAO,OAAO,MAAM,GAAG,EAAE,CAAC;AAAA,IACzD,KAAK;AACH,aAAO,OAAO,OAAO,GAAG;AAAA,IAC1B,KAAK;AACH,aAAO,MAAM,OAAO,GAAG;AAAA,IACzB,KAAK;AACH,aAAO,MAAM,OAAO,UAAU,EAAE;AAAA,IAClC,KAAK;AACH,aAAO,MAAM,OAAO,MAAM;AAAA,IAC5B,KAAK;AACH,aAAO,SAAS,OAAO,KAAK;AAAA,IAC9B,KAAK;AACH,aAAO,OAAO,OAAO,UAAU,OAAO,GAAG;AAAA,IAC3C,KAAK;AACH,aAAO,MAAM,OAAO,aAAa,UAAU,OAAO,aAAa,GAAG;AAAA,IACpE,KAAK;AACH,aAAO,MAAM,KAAK,UAAU,OAAO,OAAO,EAAE,MAAM,GAAG,EAAE,CAAC;AAAA,EAC5D;AACF;","names":[]}
@@ -0,0 +1,76 @@
1
+ /**
2
+ * features/files/handler/hooks/useFileUpload.ts
3
+ *
4
+ * The single React-side upload primitive.
5
+ *
6
+ * upload(source, opts) — one file. Returns NormalizedFile.
7
+ * uploadMany(files, opts) — many files, with the duplicate-detection
8
+ * pre-flight dialog. Returns
9
+ * { uploaded, failed, cancelled }.
10
+ *
11
+ * Single-file uploads route through cloudUpload (Python POST +
12
+ * optimistic Redux dispatch + optional share-link creation). Multi-file
13
+ * uploads route through `requestUpload` from `<UploadGuardHost/>` so the
14
+ * SHA-256 dedup pre-flight + DuplicateUploadDialog UX is consistent for
15
+ * every multi-file drop.
16
+ *
17
+ * Anonymous users (visitors on `/p/[slug]`) work via the standard cloud-
18
+ * files JWT path because Supabase issues an anonymous auth UUID for
19
+ * them. The handler does not branch on anonymous vs authenticated.
20
+ */
21
+ import type { UploadFilesArg } from "../../types.js";
22
+ import type { FileSource, UploadedNormalizedFile, UploadOpts } from "../types.js";
23
+ export interface UploadProgress {
24
+ loaded: number;
25
+ total: number;
26
+ ratio: number;
27
+ }
28
+ export interface MultiUploadOptions extends Omit<UploadFilesArg, "files" | "parentFolderId" | "visibility"> {
29
+ /** Parent cloud-files folder id. null = root. */
30
+ parentFolderId?: string | null;
31
+ /** Default visibility for every file in the batch. */
32
+ visibility?: UploadFilesArg["visibility"];
33
+ }
34
+ export interface MultiUploadResult {
35
+ /** cld_files UUIDs for every successful upload. */
36
+ uploaded: string[];
37
+ /** Per-file failure with the real backend error message. */
38
+ failed: Array<{
39
+ name: string;
40
+ error: string;
41
+ }>;
42
+ /** True when the user dismissed the duplicate-upload dialog. */
43
+ cancelled: boolean;
44
+ /**
45
+ * Files the user chose to attach as the EXISTING copy (via "Use
46
+ * existing" in the duplicate dialog) instead of re-uploading. Each
47
+ * entry carries the index into the original `files` argument plus
48
+ * the live `cld_files.id` the caller should wire into its parent
49
+ * context (chat attachment, agent resource, etc.). Follows the
50
+ * `duplicate_of_file_id` chain so dup ids resolve to live keepers.
51
+ *
52
+ * Empty for the normal no-conflict path. See
53
+ * `features/files/upload/uploadGuardOpeners.ts` for the underlying
54
+ * shape.
55
+ */
56
+ aliased: Array<{
57
+ inputIndex: number;
58
+ existingFileId: string;
59
+ }>;
60
+ }
61
+ export interface UseFileUploadResult {
62
+ /** Upload one file (or any FileSource — paste, blob, base64, url). */
63
+ upload: (source: FileSource, opts?: UploadOpts) => Promise<UploadedNormalizedFile>;
64
+ /**
65
+ * Upload many files at once. Runs the SHA-256 dedup pre-flight and shows
66
+ * the duplicate-resolution dialog when conflicts are found. Returns
67
+ * per-file success/failure so callers can render their own progress UI.
68
+ */
69
+ uploadMany: (files: File[], opts?: MultiUploadOptions) => Promise<MultiUploadResult>;
70
+ uploading: boolean;
71
+ progress: UploadProgress | null;
72
+ result: UploadedNormalizedFile | null;
73
+ error: Error | null;
74
+ reset: () => void;
75
+ }
76
+ export declare function useFileUpload(): UseFileUploadResult;
@@ -0,0 +1,67 @@
1
+ "use client";
2
+ import { useCallback, useState } from "react";
3
+ import { fileHandler } from "../handler.js";
4
+ import { requestUpload } from "../../upload/uploadGuardOpeners.js";
5
+ function useFileUpload() {
6
+ const [uploading, setUploading] = useState(false);
7
+ const [progress, setProgress] = useState(null);
8
+ const [result, setResult] = useState(null);
9
+ const [error, setError] = useState(null);
10
+ const upload = useCallback(
11
+ async (source, opts = {}) => {
12
+ setUploading(true);
13
+ setError(null);
14
+ setProgress(null);
15
+ try {
16
+ const normalized = await fileHandler.upload(source, {
17
+ ...opts,
18
+ onProgress: (loaded, total) => {
19
+ setProgress({
20
+ loaded,
21
+ total,
22
+ ratio: total > 0 ? loaded / total : 0
23
+ });
24
+ opts.onProgress?.(loaded, total);
25
+ }
26
+ });
27
+ setResult(normalized);
28
+ return normalized;
29
+ } catch (err) {
30
+ const e = err instanceof Error ? err : new Error(String(err));
31
+ setError(e);
32
+ throw e;
33
+ } finally {
34
+ setUploading(false);
35
+ }
36
+ },
37
+ []
38
+ );
39
+ const uploadMany = useCallback(
40
+ async (files, opts = {}) => {
41
+ const arg = {
42
+ files,
43
+ parentFolderId: opts.parentFolderId ?? null,
44
+ folderPath: opts.folderPath ?? null,
45
+ visibility: opts.visibility ?? "personal",
46
+ shareWith: opts.shareWith,
47
+ shareLevel: opts.shareLevel,
48
+ changeSummary: opts.changeSummary,
49
+ metadata: opts.metadata,
50
+ options: opts.options,
51
+ concurrency: opts.concurrency
52
+ };
53
+ return requestUpload(arg);
54
+ },
55
+ []
56
+ );
57
+ const reset = useCallback(() => {
58
+ setResult(null);
59
+ setError(null);
60
+ setProgress(null);
61
+ }, []);
62
+ return { upload, uploadMany, uploading, progress, result, error, reset };
63
+ }
64
+ export {
65
+ useFileUpload
66
+ };
67
+ //# sourceMappingURL=useFileUpload.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../../../../src/files/engine/handler/hooks/useFileUpload.ts"],"sourcesContent":["/**\n * features/files/handler/hooks/useFileUpload.ts\n *\n * The single React-side upload primitive.\n *\n * upload(source, opts) — one file. Returns NormalizedFile.\n * uploadMany(files, opts) — many files, with the duplicate-detection\n * pre-flight dialog. Returns\n * { uploaded, failed, cancelled }.\n *\n * Single-file uploads route through cloudUpload (Python POST +\n * optimistic Redux dispatch + optional share-link creation). Multi-file\n * uploads route through `requestUpload` from `<UploadGuardHost/>` so the\n * SHA-256 dedup pre-flight + DuplicateUploadDialog UX is consistent for\n * every multi-file drop.\n *\n * Anonymous users (visitors on `/p/[slug]`) work via the standard cloud-\n * files JWT path because Supabase issues an anonymous auth UUID for\n * them. The handler does not branch on anonymous vs authenticated.\n */\n\n\"use client\";\n\nimport { useCallback, useState } from \"react\";\nimport { fileHandler } from \"../handler\";\nimport { requestUpload } from \"../../upload/uploadGuardOpeners\";\nimport type { UploadFilesArg } from \"../../types\";\nimport type {\n FileSource,\n UploadedNormalizedFile,\n UploadOpts,\n} from \"../types\";\n\nexport interface UploadProgress {\n loaded: number;\n total: number;\n ratio: number;\n}\n\nexport interface MultiUploadOptions extends Omit<\n UploadFilesArg,\n \"files\" | \"parentFolderId\" | \"visibility\"\n> {\n /** Parent cloud-files folder id. null = root. */\n parentFolderId?: string | null;\n /** Default visibility for every file in the batch. */\n visibility?: UploadFilesArg[\"visibility\"];\n}\n\nexport interface MultiUploadResult {\n /** cld_files UUIDs for every successful upload. */\n uploaded: string[];\n /** Per-file failure with the real backend error message. */\n failed: Array<{ name: string; error: string }>;\n /** True when the user dismissed the duplicate-upload dialog. */\n cancelled: boolean;\n /**\n * Files the user chose to attach as the EXISTING copy (via \"Use\n * existing\" in the duplicate dialog) instead of re-uploading. Each\n * entry carries the index into the original `files` argument plus\n * the live `cld_files.id` the caller should wire into its parent\n * context (chat attachment, agent resource, etc.). Follows the\n * `duplicate_of_file_id` chain so dup ids resolve to live keepers.\n *\n * Empty for the normal no-conflict path. See\n * `features/files/upload/uploadGuardOpeners.ts` for the underlying\n * shape.\n */\n aliased: Array<{ inputIndex: number; existingFileId: string }>;\n}\n\nexport interface UseFileUploadResult {\n /** Upload one file (or any FileSource — paste, blob, base64, url). */\n upload: (\n source: FileSource,\n opts?: UploadOpts,\n ) => Promise<UploadedNormalizedFile>;\n /**\n * Upload many files at once. Runs the SHA-256 dedup pre-flight and shows\n * the duplicate-resolution dialog when conflicts are found. Returns\n * per-file success/failure so callers can render their own progress UI.\n */\n uploadMany: (\n files: File[],\n opts?: MultiUploadOptions,\n ) => Promise<MultiUploadResult>;\n uploading: boolean;\n progress: UploadProgress | null;\n result: UploadedNormalizedFile | null;\n error: Error | null;\n reset: () => void;\n}\n\nexport function useFileUpload(): UseFileUploadResult {\n const [uploading, setUploading] = useState(false);\n const [progress, setProgress] = useState<UploadProgress | null>(null);\n const [result, setResult] = useState<UploadedNormalizedFile | null>(null);\n const [error, setError] = useState<Error | null>(null);\n\n const upload = useCallback(\n async (\n source: FileSource,\n opts: UploadOpts = {},\n ): Promise<UploadedNormalizedFile> => {\n setUploading(true);\n setError(null);\n setProgress(null);\n try {\n const normalized = await fileHandler.upload(source, {\n ...opts,\n onProgress: (loaded, total) => {\n setProgress({\n loaded,\n total,\n ratio: total > 0 ? loaded / total : 0,\n });\n opts.onProgress?.(loaded, total);\n },\n });\n setResult(normalized);\n return normalized;\n } catch (err) {\n const e = err instanceof Error ? err : new Error(String(err));\n setError(e);\n throw e;\n } finally {\n setUploading(false);\n }\n },\n [],\n );\n\n const uploadMany = useCallback(\n async (\n files: File[],\n opts: MultiUploadOptions = {},\n ): Promise<MultiUploadResult> => {\n const arg: UploadFilesArg = {\n files,\n parentFolderId: opts.parentFolderId ?? null,\n folderPath: opts.folderPath ?? null,\n visibility: opts.visibility ?? \"personal\",\n shareWith: opts.shareWith,\n shareLevel: opts.shareLevel,\n changeSummary: opts.changeSummary,\n metadata: opts.metadata,\n options: opts.options,\n concurrency: opts.concurrency,\n };\n return requestUpload(arg);\n },\n [],\n );\n\n const reset = useCallback(() => {\n setResult(null);\n setError(null);\n setProgress(null);\n }, []);\n\n return { upload, uploadMany, uploading, progress, result, error, reset };\n}\n"],"mappings":";AAuBA,SAAS,aAAa,gBAAgB;AACtC,SAAS,mBAAmB;AAC5B,SAAS,qBAAqB;AAoEvB,SAAS,gBAAqC;AACnD,QAAM,CAAC,WAAW,YAAY,IAAI,SAAS,KAAK;AAChD,QAAM,CAAC,UAAU,WAAW,IAAI,SAAgC,IAAI;AACpE,QAAM,CAAC,QAAQ,SAAS,IAAI,SAAwC,IAAI;AACxE,QAAM,CAAC,OAAO,QAAQ,IAAI,SAAuB,IAAI;AAErD,QAAM,SAAS;AAAA,IACb,OACE,QACA,OAAmB,CAAC,MACgB;AACpC,mBAAa,IAAI;AACjB,eAAS,IAAI;AACb,kBAAY,IAAI;AAChB,UAAI;AACF,cAAM,aAAa,MAAM,YAAY,OAAO,QAAQ;AAAA,UAClD,GAAG;AAAA,UACH,YAAY,CAAC,QAAQ,UAAU;AAC7B,wBAAY;AAAA,cACV;AAAA,cACA;AAAA,cACA,OAAO,QAAQ,IAAI,SAAS,QAAQ;AAAA,YACtC,CAAC;AACD,iBAAK,aAAa,QAAQ,KAAK;AAAA,UACjC;AAAA,QACF,CAAC;AACD,kBAAU,UAAU;AACpB,eAAO;AAAA,MACT,SAAS,KAAK;AACZ,cAAM,IAAI,eAAe,QAAQ,MAAM,IAAI,MAAM,OAAO,GAAG,CAAC;AAC5D,iBAAS,CAAC;AACV,cAAM;AAAA,MACR,UAAE;AACA,qBAAa,KAAK;AAAA,MACpB;AAAA,IACF;AAAA,IACA,CAAC;AAAA,EACH;AAEA,QAAM,aAAa;AAAA,IACjB,OACE,OACA,OAA2B,CAAC,MACG;AAC/B,YAAM,MAAsB;AAAA,QAC1B;AAAA,QACA,gBAAgB,KAAK,kBAAkB;AAAA,QACvC,YAAY,KAAK,cAAc;AAAA,QAC/B,YAAY,KAAK,cAAc;AAAA,QAC/B,WAAW,KAAK;AAAA,QAChB,YAAY,KAAK;AAAA,QACjB,eAAe,KAAK;AAAA,QACpB,UAAU,KAAK;AAAA,QACf,SAAS,KAAK;AAAA,QACd,aAAa,KAAK;AAAA,MACpB;AACA,aAAO,cAAc,GAAG;AAAA,IAC1B;AAAA,IACA,CAAC;AAAA,EACH;AAEA,QAAM,QAAQ,YAAY,MAAM;AAC9B,cAAU,IAAI;AACd,aAAS,IAAI;AACb,gBAAY,IAAI;AAAA,EAClB,GAAG,CAAC,CAAC;AAEL,SAAO,EAAE,QAAQ,YAAY,WAAW,UAAU,QAAQ,OAAO,MAAM;AACzE;","names":[]}
@@ -0,0 +1,14 @@
1
+ /**
2
+ * features/files/handler/input/normalize.ts
3
+ *
4
+ * Pure first-pass: turn a `FileSource` into a partial `NormalizedFile`.
5
+ * "Pure" means no I/O, no Redux. Anything that needs the network or auth
6
+ * runs in the resolver, not here.
7
+ *
8
+ * Each `FileSource` kind gets its own arm. Adding a new shape is a single
9
+ * edit in this file plus a `FileSource` variant in `../types.ts`.
10
+ */
11
+ import type { CloudFile } from "../../types.js";
12
+ import type { FileSource, NormalizedFile } from "../types.js";
13
+ export declare function normalize(source: FileSource): NormalizedFile;
14
+ export declare function fromCloudFile(cloudFile: CloudFile, source: FileSource): NormalizedFile;