@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,249 @@
1
+ /**
2
+ * features/files/api/files.ts
3
+ *
4
+ * REST endpoints under /files/*. Wraps the typed client with endpoint-specific
5
+ * args/returns so thunks don't hand-craft URLs or bodies.
6
+ *
7
+ * Backend contract: features/files/cld_files_frontend.md §6 (Files).
8
+ */
9
+ import { type DownloadProgressEvent, type RequestOptions, type ResponseMeta, type UploadProgressEvent } from "../host/python-client.js";
10
+ import type { components } from "@ai-matrx/agents/generated/api-types";
11
+ import type { BulkDeleteFilesRequest, BulkMoveFilesRequest, BulkResponse, CopyFileRequest, FilePatchRequest, FileRecordApi, FileUploadResponse, PermissionLevel, RenameFileRequest, SearchFilesParams, SearchFilesResponse, Visibility } from "../types.js";
12
+ export interface UploadFileParams {
13
+ file: File;
14
+ filePath: string;
15
+ visibility?: Visibility;
16
+ shareWith?: string[];
17
+ shareLevel?: PermissionLevel;
18
+ changeSummary?: string;
19
+ metadata?: Record<string, unknown>;
20
+ /**
21
+ * Per-upload options the backend reads to alter post-upload behavior —
22
+ * notably `rag.trigger_now` to run Knowledge immediately instead of waiting for
23
+ * the scheduled auto-Knowledge sweep. Serialized to the `options_json` form
24
+ * field alongside `metadata_json`.
25
+ */
26
+ options?: UploadOptions;
27
+ /** Strict dedup intent. Omitted preserves the legacy upload behavior. */
28
+ intent?: "create" | "alias_existing" | "force_new_copy";
29
+ /** Required by the backend when `intent` is `force_new_copy`. */
30
+ reason?: string;
31
+ }
32
+ /** Mirrors the backend `options_json` envelope. Extend as new opts land. */
33
+ export interface UploadOptions {
34
+ rag?: {
35
+ /** Run Knowledge ingest immediately on upload (skip the scheduled sweep). */
36
+ trigger_now?: boolean;
37
+ };
38
+ }
39
+ export declare function uploadFile(params: UploadFileParams, opts?: RequestOptions): Promise<{
40
+ data: FileUploadResponse;
41
+ meta: ResponseMeta;
42
+ }>;
43
+ /**
44
+ * Write new bytes as the NEXT VERSION of an existing file — same id, version + 1 —
45
+ * `POST /files/{file_id}/versions`.
46
+ *
47
+ * Keyed by the file's ID, so it is the ONE save path for the owner AND for an editor
48
+ * holding an edit grant on someone else's file: the server authorizes the caller's
49
+ * `write` right on that file, stores the bytes under the file's own owner/path, records
50
+ * the caller as the version's author and keeps `changeSummary`. The request carries the
51
+ * FILE's own organization, never the picker's. Callers go through the `saveFileNewVersion`
52
+ * thunk (never `uploadFile` with a derived path — that is how a save became a second
53
+ * file, "name (1).txt", 2026-09-30).
54
+ *
55
+ * Raw multipart helper: the route is not in the pinned generated contract yet.
56
+ */
57
+ export declare function uploadNewVersion(fileId: string, params: {
58
+ file: File;
59
+ changeSummary?: string;
60
+ }, opts?: RequestOptions): Promise<{
61
+ data: FileUploadResponse;
62
+ meta: ResponseMeta;
63
+ }>;
64
+ export declare function uploadFileWithProgress(params: UploadFileParams, onProgress: (event: UploadProgressEvent) => void, opts?: RequestOptions): Promise<{
65
+ data: FileUploadResponse;
66
+ meta: ResponseMeta;
67
+ }>;
68
+ /**
69
+ * List files. Reading via supabase-js is preferred (RLS-filtered, no roundtrip
70
+ * through the backend). Exposed here for parity with the REST contract.
71
+ *
72
+ * Pagination is supported on the backend side (1–1000, default 100).
73
+ */
74
+ export declare function listFiles(params?: {
75
+ folderPath?: string;
76
+ limit?: number;
77
+ offset?: number;
78
+ }, opts?: RequestOptions): Promise<{
79
+ data: FileRecordApi[];
80
+ meta: ResponseMeta;
81
+ }>;
82
+ export declare function getFile(fileId: string, opts?: RequestOptions): Promise<{
83
+ data: FileRecordApi;
84
+ meta: ResponseMeta;
85
+ }>;
86
+ /**
87
+ * Read the canonical durable file row without asking the server to resolve
88
+ * render URLs or thumbnail variants. Exact-id metadata hydration uses this
89
+ * backend authorization boundary instead of compiling the browser RLS policy
90
+ * once per file row.
91
+ */
92
+ export declare function getFileMetadata(fileId: string, opts?: RequestOptions): Promise<{
93
+ data: FileRecordApi;
94
+ meta: ResponseMeta;
95
+ }>;
96
+ export declare function getFileByPath(filePath: string, opts?: RequestOptions): Promise<{
97
+ data: FileRecordApi;
98
+ meta: ResponseMeta;
99
+ }>;
100
+ /**
101
+ * Full tree via the RPC. Prefer calling the RPC directly via supabase-js in
102
+ * thunks (reducer dispatches don't need a round-trip through the backend).
103
+ * This is provided for backend-mediated contexts only.
104
+ */
105
+ export declare function getFileTree(opts?: RequestOptions): Promise<{
106
+ data: unknown[];
107
+ meta: ResponseMeta;
108
+ }>;
109
+ /**
110
+ * Patch a file. Union body (A.2 — landed in matrx-utils v1.1.0): can rename,
111
+ * move (`folder`), change visibility, merge metadata, grant/revoke share,
112
+ * grant/revoke permissions, request variants, restore a version, restore
113
+ * from trash, or copy — in any combination, in a single round-trip.
114
+ *
115
+ * Sub-operations run sequentially with best-effort atomicity: the upload
116
+ * piece always lands, but downstream sub-ops can fail individually and
117
+ * surface in the response envelope's `errors[]` array (FE handles partial
118
+ * success — see useFileMutation in Phase 1).
119
+ *
120
+ * **Metadata is MERGED by default** — only the keys you send are touched
121
+ * on the server-side jsonb blob. For the rare "rewrite the whole metadata
122
+ * blob" tool, use `patchFileReplaceMetadata` below.
123
+ *
124
+ * `share_revoke` and `restore_from_trash` default to false; callers only
125
+ * need to set them when intentionally invoking that sub-op.
126
+ */
127
+ export type FilePatchBody = Partial<FilePatchRequest>;
128
+ export declare function patchFile(fileId: string, body: FilePatchBody, opts?: RequestOptions): Promise<{
129
+ data: FileRecordApi;
130
+ meta: ResponseMeta;
131
+ }>;
132
+ /**
133
+ * Same as `patchFile` but uses the legacy "replace whole metadata jsonb"
134
+ * semantics. Use ONLY for explicit overwrite-the-whole-blob tools — never
135
+ * for incremental edits where you'd lose every key the user didn't send.
136
+ */
137
+ export declare function patchFileReplaceMetadata(fileId: string, body: FilePatchBody, opts?: RequestOptions): Promise<{
138
+ data: FileRecordApi;
139
+ meta: ResponseMeta;
140
+ }>;
141
+ /** Move a file to Trash (soft delete — restorable). There is no hard delete. */
142
+ export declare function deleteFile(fileId: string, opts?: RequestOptions): Promise<{
143
+ data: components["schemas"]["DeleteResponse"];
144
+ meta: ResponseMeta;
145
+ }>;
146
+ export declare function downloadFile(fileId: string, params?: {
147
+ version?: number;
148
+ inline?: boolean;
149
+ }, opts?: RequestOptions): Promise<{
150
+ blob: Blob;
151
+ filename: string | null;
152
+ meta: ResponseMeta;
153
+ }>;
154
+ /**
155
+ * Same as `downloadFile` but reports byte-level progress via `onProgress`.
156
+ * Use this when the UI needs a "Downloading 6.2 / 10 MB…" indicator —
157
+ * notably for `useFileBlob`, which feeds previewers that may pull
158
+ * multi-megabyte payloads.
159
+ */
160
+ export declare function downloadFileWithProgress(fileId: string, onProgress: (event: DownloadProgressEvent) => void, params?: {
161
+ version?: number;
162
+ inline?: boolean;
163
+ }, opts?: RequestOptions): Promise<{
164
+ blob: Blob;
165
+ filename: string | null;
166
+ meta: ResponseMeta;
167
+ }>;
168
+ /**
169
+ * Move many files to Trash (soft delete — restorable) in one
170
+ * call. Returns the standard `BulkResponse` envelope:
171
+ *
172
+ * { results: [{ id, ok, error }], succeeded: N, failed: M }
173
+ *
174
+ * Per-request size capped at the user's tier `max_bulk_items` (free=200,
175
+ * pro=500). Concurrency is capped server-side at 10 in-flight.
176
+ */
177
+ export declare function bulkDeleteFiles(body: BulkDeleteFilesRequest, opts?: RequestOptions): Promise<{
178
+ data: BulkResponse | null;
179
+ meta: ResponseMeta;
180
+ }>;
181
+ /**
182
+ * Move many files to a new parent folder in one call.
183
+ *
184
+ * The backend verifies every target file belongs to the same owner as the
185
+ * destination folder; cross-owner moves are refused with `403`. Returns
186
+ * the standard `BulkResponse` envelope.
187
+ */
188
+ export declare function bulkMoveFiles(body: BulkMoveFilesRequest, opts?: RequestOptions): Promise<{
189
+ data: BulkResponse;
190
+ meta: ResponseMeta;
191
+ }>;
192
+ /**
193
+ * Read the authenticated user's account tier + current storage usage.
194
+ * Drives the storage indicator + tier badge + feature gating in the UI.
195
+ */
196
+ export declare function getStorageUsage(opts?: RequestOptions): Promise<{
197
+ data: components["schemas"]["StorageUsageResponse"];
198
+ meta: ResponseMeta;
199
+ }>;
200
+ /**
201
+ * List soft-deleted files + folders for the authenticated user.
202
+ * Renders the trash view; pair with `restoreFile` to undo.
203
+ */
204
+ export declare function listTrash(opts?: RequestOptions): Promise<{
205
+ data: components["schemas"]["TrashListResponse"];
206
+ meta: ResponseMeta;
207
+ }>;
208
+ /**
209
+ * Restore a soft-deleted file. Owner or admin grantee only — others
210
+ * get `403 permission_denied`.
211
+ */
212
+ export declare function restoreFile(fileId: string, opts?: RequestOptions): Promise<{
213
+ data: FileRecordApi;
214
+ meta: ResponseMeta;
215
+ }>;
216
+ /**
217
+ * Filename / path substring search across the authenticated user's
218
+ * non-deleted files. `mimePrefix` filters by `mime_type LIKE 'prefix%'`
219
+ * (e.g. `image/`, `video/mp4`).
220
+ */
221
+ export declare function searchFiles(params: SearchFilesParams, opts?: RequestOptions): Promise<{
222
+ data: SearchFilesResponse;
223
+ meta: ResponseMeta;
224
+ }>;
225
+ /**
226
+ * Rename / move a file by changing its full logical path. The backend
227
+ * auto-creates any missing parent folders. Replaces the FE's earlier
228
+ * "metadata hack" approach.
229
+ *
230
+ * The new path is the FULL path including the filename — e.g.
231
+ * `Reports/2026/Q1/forecast.pdf`. Just changing the leaf renames in
232
+ * place; changing intermediate segments moves into a different folder.
233
+ */
234
+ export declare function renameFile(fileId: string, body: RenameFileRequest, opts?: RequestOptions): Promise<{
235
+ data: FileRecordApi;
236
+ meta: ResponseMeta;
237
+ }>;
238
+ /**
239
+ * Copy a file to a new logical path. Backend auto-creates parent folders.
240
+ * The copy counts against the caller's storage + file-count quota; refused
241
+ * uploads come back as 413 with the relevant quota code.
242
+ *
243
+ * `overwrite: true` replaces an existing file at `target_path`; the
244
+ * default (false) returns `409 file_already_exists` on conflict.
245
+ */
246
+ export declare function copyFile(fileId: string, body: CopyFileRequest, opts?: RequestOptions): Promise<{
247
+ data: FileUploadResponse;
248
+ meta: ResponseMeta;
249
+ }>;
@@ -0,0 +1,231 @@
1
+ import {
2
+ delJson,
3
+ downloadBlob,
4
+ downloadBlobWithProgress,
5
+ getJson,
6
+ patchJson,
7
+ postJson,
8
+ postMultipart,
9
+ uploadWithProgress
10
+ } from "../host/python-client.js";
11
+ import {
12
+ apiDelete,
13
+ apiGet,
14
+ apiMultipart,
15
+ apiPost,
16
+ buildPath
17
+ } from "../host/typed-client.js";
18
+ import {
19
+ rememberFileOrganizationFrom,
20
+ withFileOrganization
21
+ } from "./fileOrganization.js";
22
+ function appendUploadIntent(form, params) {
23
+ if (params.intent) form.append("intent", params.intent);
24
+ if (params.reason) form.append("reason", params.reason);
25
+ }
26
+ async function uploadFile(params, opts = {}) {
27
+ const form = new FormData();
28
+ form.append("file", params.file);
29
+ form.append("file_path", params.filePath);
30
+ if (params.visibility) form.append("visibility", params.visibility);
31
+ if (params.shareWith?.length)
32
+ form.append("share_with", params.shareWith.join(","));
33
+ if (params.shareLevel) form.append("share_level", params.shareLevel);
34
+ if (params.changeSummary) form.append("change_summary", params.changeSummary);
35
+ if (params.metadata)
36
+ form.append("metadata_json", JSON.stringify(params.metadata));
37
+ if (params.options)
38
+ form.append("options_json", JSON.stringify(params.options));
39
+ appendUploadIntent(form, params);
40
+ return apiMultipart("/files/upload", form, opts);
41
+ }
42
+ async function uploadNewVersion(fileId, params, opts = {}) {
43
+ const form = new FormData();
44
+ form.append("file", params.file);
45
+ if (params.changeSummary) form.append("change_summary", params.changeSummary);
46
+ return postMultipart(
47
+ `/files/${encodeURIComponent(fileId)}/versions`,
48
+ form,
49
+ withFileOrganization(fileId, opts)
50
+ );
51
+ }
52
+ async function uploadFileWithProgress(params, onProgress, opts = {}) {
53
+ const form = new FormData();
54
+ form.append("file", params.file);
55
+ form.append("file_path", params.filePath);
56
+ if (params.visibility) form.append("visibility", params.visibility);
57
+ if (params.shareWith?.length)
58
+ form.append("share_with", params.shareWith.join(","));
59
+ if (params.shareLevel) form.append("share_level", params.shareLevel);
60
+ if (params.changeSummary) form.append("change_summary", params.changeSummary);
61
+ if (params.metadata)
62
+ form.append("metadata_json", JSON.stringify(params.metadata));
63
+ if (params.options)
64
+ form.append("options_json", JSON.stringify(params.options));
65
+ appendUploadIntent(form, params);
66
+ return uploadWithProgress(
67
+ "/files/upload",
68
+ form,
69
+ onProgress,
70
+ opts
71
+ );
72
+ }
73
+ async function listFiles(params = {}, opts = {}) {
74
+ return apiGet("/files", {
75
+ ...opts,
76
+ query: {
77
+ folder_path: params.folderPath,
78
+ limit: params.limit,
79
+ offset: params.offset
80
+ }
81
+ });
82
+ }
83
+ async function getFile(fileId, opts = {}) {
84
+ const answered = await apiGet(
85
+ buildPath("/files/{file_id}", { file_id: fileId }),
86
+ withFileOrganization(fileId, opts)
87
+ );
88
+ rememberFileOrganizationFrom(answered.data);
89
+ return answered;
90
+ }
91
+ async function getFileMetadata(fileId, opts = {}) {
92
+ const answered = await apiGet(buildPath("/files/{file_id}", { file_id: fileId }), {
93
+ ...withFileOrganization(fileId, opts),
94
+ query: { include_urls: false }
95
+ });
96
+ rememberFileOrganizationFrom(answered.data);
97
+ return answered;
98
+ }
99
+ async function getFileByPath(filePath, opts = {}) {
100
+ return apiGet(
101
+ buildPath("/files/by-path/{file_path}", { file_path: filePath }),
102
+ opts
103
+ );
104
+ }
105
+ async function getFileTree(opts = {}) {
106
+ return apiGet("/files/tree", opts);
107
+ }
108
+ async function patchFile(fileId, body, opts = {}) {
109
+ const fullBody = {
110
+ share_revoke: false,
111
+ restore_from_trash: false,
112
+ ...body
113
+ };
114
+ return patchJson(
115
+ `/files/${fileId}`,
116
+ fullBody,
117
+ withFileOrganization(fileId, opts)
118
+ );
119
+ }
120
+ async function patchFileReplaceMetadata(fileId, body, opts = {}) {
121
+ const fullBody = {
122
+ share_revoke: false,
123
+ restore_from_trash: false,
124
+ ...body
125
+ };
126
+ return patchJson(
127
+ `/files/${fileId}?metadata_merge=false`,
128
+ fullBody,
129
+ withFileOrganization(fileId, opts)
130
+ );
131
+ }
132
+ async function deleteFile(fileId, opts = {}) {
133
+ return apiDelete(
134
+ buildPath("/files/{file_id}", { file_id: fileId }),
135
+ withFileOrganization(fileId, opts)
136
+ );
137
+ }
138
+ async function downloadFile(fileId, params = {}, opts = {}) {
139
+ const qs = [];
140
+ if (params.version !== void 0) qs.push(`version=${params.version}`);
141
+ if (params.inline) qs.push("inline=true");
142
+ const q = qs.length ? `?${qs.join("&")}` : "";
143
+ return downloadBlob(
144
+ `/files/${fileId}/download${q}`,
145
+ withFileOrganization(fileId, opts)
146
+ );
147
+ }
148
+ async function downloadFileWithProgress(fileId, onProgress, params = {}, opts = {}) {
149
+ const qs = [];
150
+ if (params.version !== void 0) qs.push(`version=${params.version}`);
151
+ if (params.inline) qs.push("inline=true");
152
+ const q = qs.length ? `?${qs.join("&")}` : "";
153
+ return downloadBlobWithProgress(
154
+ `/files/${fileId}/download${q}`,
155
+ onProgress,
156
+ withFileOrganization(fileId, opts)
157
+ );
158
+ }
159
+ async function bulkDeleteFiles(body, opts = {}) {
160
+ return delJson(
161
+ "/files/bulk",
162
+ body,
163
+ opts
164
+ );
165
+ }
166
+ async function bulkMoveFiles(body, opts = {}) {
167
+ return postJson(
168
+ "/files/bulk/move",
169
+ body,
170
+ opts
171
+ );
172
+ }
173
+ async function getStorageUsage(opts = {}) {
174
+ return apiGet("/files/usage", opts);
175
+ }
176
+ async function listTrash(opts = {}) {
177
+ return apiGet("/files/trash", opts);
178
+ }
179
+ async function restoreFile(fileId, opts = {}) {
180
+ return postJson(
181
+ `/files/${fileId}/restore`,
182
+ {},
183
+ withFileOrganization(fileId, opts)
184
+ );
185
+ }
186
+ async function searchFiles(params, opts = {}) {
187
+ const qs = [`q=${encodeURIComponent(params.q)}`];
188
+ if (params.mimePrefix)
189
+ qs.push(`mime_prefix=${encodeURIComponent(params.mimePrefix)}`);
190
+ if (params.limit !== void 0) qs.push(`limit=${params.limit}`);
191
+ if (params.offset !== void 0) qs.push(`offset=${params.offset}`);
192
+ return getJson(`/files/search?${qs.join("&")}`, opts);
193
+ }
194
+ async function renameFile(fileId, body, opts = {}) {
195
+ return apiPost(
196
+ buildPath("/files/{file_id}/rename", { file_id: fileId }),
197
+ body,
198
+ withFileOrganization(fileId, opts)
199
+ );
200
+ }
201
+ async function copyFile(fileId, body, opts = {}) {
202
+ return apiPost(
203
+ buildPath("/files/{file_id}/copy", { file_id: fileId }),
204
+ body,
205
+ opts
206
+ );
207
+ }
208
+ export {
209
+ bulkDeleteFiles,
210
+ bulkMoveFiles,
211
+ copyFile,
212
+ deleteFile,
213
+ downloadFile,
214
+ downloadFileWithProgress,
215
+ getFile,
216
+ getFileByPath,
217
+ getFileMetadata,
218
+ getFileTree,
219
+ getStorageUsage,
220
+ listFiles,
221
+ listTrash,
222
+ patchFile,
223
+ patchFileReplaceMetadata,
224
+ renameFile,
225
+ restoreFile,
226
+ searchFiles,
227
+ uploadFile,
228
+ uploadFileWithProgress,
229
+ uploadNewVersion
230
+ };
231
+ //# sourceMappingURL=files.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../../../src/files/engine/api/files.ts"],"sourcesContent":["/**\n * features/files/api/files.ts\n *\n * REST endpoints under /files/*. Wraps the typed client with endpoint-specific\n * args/returns so thunks don't hand-craft URLs or bodies.\n *\n * Backend contract: features/files/cld_files_frontend.md §6 (Files).\n */\n\nimport {\n delJson,\n downloadBlob,\n downloadBlobWithProgress,\n getJson,\n patchJson,\n postJson,\n postMultipart,\n uploadWithProgress,\n type DownloadProgressEvent,\n type RequestOptions,\n type ResponseMeta,\n type UploadProgressEvent,\n} from \"../host/python-client\";\nimport {\n apiDelete,\n apiGet,\n apiMultipart,\n apiPost,\n buildPath,\n} from \"../host/typed-client\";\nimport type { components } from \"@ai-matrx/agents/generated/api-types\";\nimport {\n rememberFileOrganizationFrom,\n withFileOrganization,\n} from \"./fileOrganization\";\nimport type {\n BulkDeleteFilesRequest,\n BulkMoveFilesRequest,\n BulkResponse,\n CopyFileRequest,\n FilePatchRequest,\n FileRecordApi,\n FileUploadResponse,\n PermissionLevel,\n RenameFileRequest,\n SearchFilesParams,\n SearchFilesResponse,\n Visibility,\n} from \"../types\";\n\n// ---------------------------------------------------------------------------\n// Upload\n// ---------------------------------------------------------------------------\n\nexport interface UploadFileParams {\n file: File;\n filePath: string;\n visibility?: Visibility;\n shareWith?: string[];\n shareLevel?: PermissionLevel;\n changeSummary?: string;\n metadata?: Record<string, unknown>;\n /**\n * Per-upload options the backend reads to alter post-upload behavior —\n * notably `rag.trigger_now` to run Knowledge immediately instead of waiting for\n * the scheduled auto-Knowledge sweep. Serialized to the `options_json` form\n * field alongside `metadata_json`.\n */\n options?: UploadOptions;\n /** Strict dedup intent. Omitted preserves the legacy upload behavior. */\n intent?: \"create\" | \"alias_existing\" | \"force_new_copy\";\n /** Required by the backend when `intent` is `force_new_copy`. */\n reason?: string;\n}\n\nfunction appendUploadIntent(form: FormData, params: UploadFileParams): void {\n if (params.intent) form.append(\"intent\", params.intent);\n if (params.reason) form.append(\"reason\", params.reason);\n}\n\n/** Mirrors the backend `options_json` envelope. Extend as new opts land. */\nexport interface UploadOptions {\n rag?: {\n /** Run Knowledge ingest immediately on upload (skip the scheduled sweep). */\n trigger_now?: boolean;\n };\n}\n\nexport async function uploadFile(\n params: UploadFileParams,\n opts: RequestOptions = {},\n): Promise<{ data: FileUploadResponse; meta: ResponseMeta }> {\n const form = new FormData();\n form.append(\"file\", params.file);\n form.append(\"file_path\", params.filePath);\n if (params.visibility) form.append(\"visibility\", params.visibility);\n if (params.shareWith?.length)\n form.append(\"share_with\", params.shareWith.join(\",\"));\n if (params.shareLevel) form.append(\"share_level\", params.shareLevel);\n if (params.changeSummary) form.append(\"change_summary\", params.changeSummary);\n if (params.metadata)\n form.append(\"metadata_json\", JSON.stringify(params.metadata));\n if (params.options)\n form.append(\"options_json\", JSON.stringify(params.options));\n appendUploadIntent(form, params);\n\n // Non-progress multipart POST with a JSON response → typed client. Response\n // (FileUploadResponse) is DERIVED from the contract, not asserted.\n return apiMultipart(\"/files/upload\", form, opts);\n}\n\n/**\n * Write new bytes as the NEXT VERSION of an existing file — same id, version + 1 —\n * `POST /files/{file_id}/versions`.\n *\n * Keyed by the file's ID, so it is the ONE save path for the owner AND for an editor\n * holding an edit grant on someone else's file: the server authorizes the caller's\n * `write` right on that file, stores the bytes under the file's own owner/path, records\n * the caller as the version's author and keeps `changeSummary`. The request carries the\n * FILE's own organization, never the picker's. Callers go through the `saveFileNewVersion`\n * thunk (never `uploadFile` with a derived path — that is how a save became a second\n * file, \"name (1).txt\", 2026-09-30).\n *\n * Raw multipart helper: the route is not in the pinned generated contract yet.\n */\nexport async function uploadNewVersion(\n fileId: string,\n params: { file: File; changeSummary?: string },\n opts: RequestOptions = {},\n): Promise<{ data: FileUploadResponse; meta: ResponseMeta }> {\n const form = new FormData();\n form.append(\"file\", params.file);\n if (params.changeSummary) form.append(\"change_summary\", params.changeSummary);\n return postMultipart<FileUploadResponse>(\n `/files/${encodeURIComponent(fileId)}/versions`,\n form,\n withFileOrganization(fileId, opts),\n );\n}\n\nexport async function uploadFileWithProgress(\n params: UploadFileParams,\n onProgress: (event: UploadProgressEvent) => void,\n opts: RequestOptions = {},\n): Promise<{ data: FileUploadResponse; meta: ResponseMeta }> {\n const form = new FormData();\n form.append(\"file\", params.file);\n form.append(\"file_path\", params.filePath);\n if (params.visibility) form.append(\"visibility\", params.visibility);\n if (params.shareWith?.length)\n form.append(\"share_with\", params.shareWith.join(\",\"));\n if (params.shareLevel) form.append(\"share_level\", params.shareLevel);\n if (params.changeSummary) form.append(\"change_summary\", params.changeSummary);\n if (params.metadata)\n form.append(\"metadata_json\", JSON.stringify(params.metadata));\n if (params.options)\n form.append(\"options_json\", JSON.stringify(params.options));\n appendUploadIntent(form, params);\n\n // Raw client: XHR progress upload — the typed client is JSON-only and has\n // no progress-capable multipart verb.\n return uploadWithProgress<FileUploadResponse>(\n \"/files/upload\",\n form,\n onProgress,\n opts,\n );\n}\n\n// ---------------------------------------------------------------------------\n// Read\n// ---------------------------------------------------------------------------\n\n/**\n * List files. Reading via supabase-js is preferred (RLS-filtered, no roundtrip\n * through the backend). Exposed here for parity with the REST contract.\n *\n * Pagination is supported on the backend side (1–1000, default 100).\n */\nexport async function listFiles(\n params: { folderPath?: string; limit?: number; offset?: number } = {},\n opts: RequestOptions = {},\n): Promise<{ data: FileRecordApi[]; meta: ResponseMeta }> {\n return apiGet(\"/files\", {\n ...opts,\n query: {\n folder_path: params.folderPath,\n limit: params.limit,\n offset: params.offset,\n },\n });\n}\n\nexport async function getFile(\n fileId: string,\n opts: RequestOptions = {},\n): Promise<{ data: FileRecordApi; meta: ResponseMeta }> {\n // The file's own organization, never the picker's (GATES-TAIL, VERIFIER-21 #2).\n const answered = await apiGet(\n buildPath(\"/files/{file_id}\", { file_id: fileId }),\n withFileOrganization(fileId, opts),\n );\n rememberFileOrganizationFrom(answered.data);\n return answered;\n}\n\n/**\n * Read the canonical durable file row without asking the server to resolve\n * render URLs or thumbnail variants. Exact-id metadata hydration uses this\n * backend authorization boundary instead of compiling the browser RLS policy\n * once per file row.\n */\nexport async function getFileMetadata(\n fileId: string,\n opts: RequestOptions = {},\n): Promise<{ data: FileRecordApi; meta: ResponseMeta }> {\n const answered = await apiGet(buildPath(\"/files/{file_id}\", { file_id: fileId }), {\n ...withFileOrganization(fileId, opts),\n query: { include_urls: false },\n });\n rememberFileOrganizationFrom(answered.data);\n return answered;\n}\n\nexport async function getFileByPath(\n filePath: string,\n opts: RequestOptions = {},\n): Promise<{ data: FileRecordApi; meta: ResponseMeta }> {\n return apiGet(\n buildPath(\"/files/by-path/{file_path}\", { file_path: filePath }),\n opts,\n );\n}\n\n/**\n * Full tree via the RPC. Prefer calling the RPC directly via supabase-js in\n * thunks (reducer dispatches don't need a round-trip through the backend).\n * This is provided for backend-mediated contexts only.\n */\nexport async function getFileTree(\n opts: RequestOptions = {},\n): Promise<{ data: unknown[]; meta: ResponseMeta }> {\n return apiGet(\"/files/tree\", opts);\n}\n\n// ---------------------------------------------------------------------------\n// Mutate\n// ---------------------------------------------------------------------------\n\n/**\n * Patch a file. Union body (A.2 — landed in matrx-utils v1.1.0): can rename,\n * move (`folder`), change visibility, merge metadata, grant/revoke share,\n * grant/revoke permissions, request variants, restore a version, restore\n * from trash, or copy — in any combination, in a single round-trip.\n *\n * Sub-operations run sequentially with best-effort atomicity: the upload\n * piece always lands, but downstream sub-ops can fail individually and\n * surface in the response envelope's `errors[]` array (FE handles partial\n * success — see useFileMutation in Phase 1).\n *\n * **Metadata is MERGED by default** — only the keys you send are touched\n * on the server-side jsonb blob. For the rare \"rewrite the whole metadata\n * blob\" tool, use `patchFileReplaceMetadata` below.\n *\n * `share_revoke` and `restore_from_trash` default to false; callers only\n * need to set them when intentionally invoking that sub-op.\n */\nexport type FilePatchBody = Partial<FilePatchRequest>;\n\nexport async function patchFile(\n fileId: string,\n body: FilePatchBody,\n opts: RequestOptions = {},\n): Promise<{ data: FileRecordApi; meta: ResponseMeta }> {\n const fullBody: FilePatchRequest = {\n share_revoke: false,\n restore_from_trash: false,\n ...body,\n };\n // Raw client: the contract types this 200 as `FileRecord | AssetCombinedOpResponse`\n // (union-body sub-ops / X-Idempotency-Key path). Consumers here deref plain\n // `FileRecord`, so routing through the typed client would surface the union\n // and break them. Kept raw pending a consumer that handles both arms.\n return patchJson<FileRecordApi, FilePatchRequest>(\n `/files/${fileId}`,\n fullBody,\n withFileOrganization(fileId, opts),\n );\n}\n\n/**\n * Same as `patchFile` but uses the legacy \"replace whole metadata jsonb\"\n * semantics. Use ONLY for explicit overwrite-the-whole-blob tools — never\n * for incremental edits where you'd lose every key the user didn't send.\n */\nexport async function patchFileReplaceMetadata(\n fileId: string,\n body: FilePatchBody,\n opts: RequestOptions = {},\n): Promise<{ data: FileRecordApi; meta: ResponseMeta }> {\n const fullBody: FilePatchRequest = {\n share_revoke: false,\n restore_from_trash: false,\n ...body,\n };\n // Raw client: same union-response reason as `patchFile`.\n return patchJson<FileRecordApi, FilePatchRequest>(\n `/files/${fileId}?metadata_merge=false`,\n fullBody,\n withFileOrganization(fileId, opts),\n );\n}\n\n/** Move a file to Trash (soft delete — restorable). There is no hard delete. */\nexport async function deleteFile(\n fileId: string,\n opts: RequestOptions = {},\n): Promise<{\n data: components[\"schemas\"][\"DeleteResponse\"];\n meta: ResponseMeta;\n}> {\n return apiDelete(\n buildPath(\"/files/{file_id}\", { file_id: fileId }),\n withFileOrganization(fileId, opts),\n );\n}\n\n// ---------------------------------------------------------------------------\n// Bytes\n// ---------------------------------------------------------------------------\n\n// Raw client: `downloadFile` / `downloadFileWithProgress` stream file BYTES\n// (Blob), not JSON — the typed client only wraps JSON verbs.\nexport async function downloadFile(\n fileId: string,\n params: { version?: number; inline?: boolean } = {},\n opts: RequestOptions = {},\n): Promise<{ blob: Blob; filename: string | null; meta: ResponseMeta }> {\n const qs: string[] = [];\n if (params.version !== undefined) qs.push(`version=${params.version}`);\n // `?inline=true` only honoured for image/video/audio/PDF — other types\n // are served as `attachment` regardless. Useful for `<a href>` previews\n // where we want the browser tab to render rather than auto-download.\n if (params.inline) qs.push(\"inline=true\");\n const q = qs.length ? `?${qs.join(\"&\")}` : \"\";\n return downloadBlob(\n `/files/${fileId}/download${q}`,\n withFileOrganization(fileId, opts),\n );\n}\n\n/**\n * Same as `downloadFile` but reports byte-level progress via `onProgress`.\n * Use this when the UI needs a \"Downloading 6.2 / 10 MB…\" indicator —\n * notably for `useFileBlob`, which feeds previewers that may pull\n * multi-megabyte payloads.\n */\nexport async function downloadFileWithProgress(\n fileId: string,\n onProgress: (event: DownloadProgressEvent) => void,\n params: { version?: number; inline?: boolean } = {},\n opts: RequestOptions = {},\n): Promise<{ blob: Blob; filename: string | null; meta: ResponseMeta }> {\n const qs: string[] = [];\n if (params.version !== undefined) qs.push(`version=${params.version}`);\n if (params.inline) qs.push(\"inline=true\");\n const q = qs.length ? `?${qs.join(\"&\")}` : \"\";\n return downloadBlobWithProgress(\n `/files/${fileId}/download${q}`,\n onProgress,\n withFileOrganization(fileId, opts),\n );\n}\n\n// ---------------------------------------------------------------------------\n// Bulk operations\n// ---------------------------------------------------------------------------\n\n/**\n * Move many files to Trash (soft delete — restorable) in one\n * call. Returns the standard `BulkResponse` envelope:\n *\n * { results: [{ id, ok, error }], succeeded: N, failed: M }\n *\n * Per-request size capped at the user's tier `max_bulk_items` (free=200,\n * pro=500). Concurrency is capped server-side at 10 in-flight.\n */\nexport async function bulkDeleteFiles(\n body: BulkDeleteFilesRequest,\n opts: RequestOptions = {},\n): Promise<{ data: BulkResponse | null; meta: ResponseMeta }> {\n // Raw client: DELETE-with-body. The typed client's `apiDelete` takes no\n // request body, so a bulk delete can't route through it.\n return delJson<BulkResponse, BulkDeleteFilesRequest>(\n \"/files/bulk\",\n body,\n opts,\n );\n}\n\n/**\n * Move many files to a new parent folder in one call.\n *\n * The backend verifies every target file belongs to the same owner as the\n * destination folder; cross-owner moves are refused with `403`. Returns\n * the standard `BulkResponse` envelope.\n */\nexport async function bulkMoveFiles(\n body: BulkMoveFilesRequest,\n opts: RequestOptions = {},\n): Promise<{ data: BulkResponse; meta: ResponseMeta }> {\n // Raw client: the contract's `BulkResultItem.error` is optional, but this\n // wrapper + its thunk consumers treat `BulkResponse.results[].error` as\n // required (`string | null`). Binding to the contract type would ripple that\n // optionality into the thunk payload + `toastBulkPartialFailure` outside\n // this file, so it's left raw pending a coordinated BulkResponse alignment.\n return postJson<BulkResponse, BulkMoveFilesRequest>(\n \"/files/bulk/move\",\n body,\n opts,\n );\n}\n\n// ---------------------------------------------------------------------------\n// Tier / quotas / usage\n// ---------------------------------------------------------------------------\n\n/**\n * Read the authenticated user's account tier + current storage usage.\n * Drives the storage indicator + tier badge + feature gating in the UI.\n */\nexport async function getStorageUsage(opts: RequestOptions = {}): Promise<{\n data: components[\"schemas\"][\"StorageUsageResponse\"];\n meta: ResponseMeta;\n}> {\n return apiGet(\"/files/usage\", opts);\n}\n\n// ---------------------------------------------------------------------------\n// Trash + restore\n// ---------------------------------------------------------------------------\n\n/**\n * List soft-deleted files + folders for the authenticated user.\n * Renders the trash view; pair with `restoreFile` to undo.\n */\nexport async function listTrash(opts: RequestOptions = {}): Promise<{\n data: components[\"schemas\"][\"TrashListResponse\"];\n meta: ResponseMeta;\n}> {\n return apiGet(\"/files/trash\", opts);\n}\n\n/**\n * Restore a soft-deleted file. Owner or admin grantee only — others\n * get `403 permission_denied`.\n */\nexport async function restoreFile(\n fileId: string,\n opts: RequestOptions = {},\n): Promise<{ data: FileRecordApi; meta: ResponseMeta }> {\n // Raw client: this operation declares NO request body in the contract\n // (`apiPost` would demand `body: never`), and its 200 is `DeleteResponse`,\n // not `FileRecord` — see the drift note in the module report. Left raw so the\n // existing FileRecordApi assertion + empty-body POST are preserved verbatim.\n return postJson<FileRecordApi, Record<string, never>>(\n `/files/${fileId}/restore`,\n {},\n withFileOrganization(fileId, opts),\n );\n}\n\n// ---------------------------------------------------------------------------\n// Search\n// ---------------------------------------------------------------------------\n\n/**\n * Filename / path substring search across the authenticated user's\n * non-deleted files. `mimePrefix` filters by `mime_type LIKE 'prefix%'`\n * (e.g. `image/`, `video/mp4`).\n */\nexport async function searchFiles(\n params: SearchFilesParams,\n opts: RequestOptions = {},\n): Promise<{ data: SearchFilesResponse; meta: ResponseMeta }> {\n // Raw client: this call sends `q=` unconditionally (empty string included),\n // but the typed client's query serializer DROPS empty-string values — which\n // would change the wire request for an empty query. Left raw to preserve the\n // exact `?q=` behavior.\n const qs: string[] = [`q=${encodeURIComponent(params.q)}`];\n if (params.mimePrefix)\n qs.push(`mime_prefix=${encodeURIComponent(params.mimePrefix)}`);\n if (params.limit !== undefined) qs.push(`limit=${params.limit}`);\n if (params.offset !== undefined) qs.push(`offset=${params.offset}`);\n return getJson<SearchFilesResponse>(`/files/search?${qs.join(\"&\")}`, opts);\n}\n\n// ---------------------------------------------------------------------------\n// Rename + copy (path-changing operations)\n// ---------------------------------------------------------------------------\n\n/**\n * Rename / move a file by changing its full logical path. The backend\n * auto-creates any missing parent folders. Replaces the FE's earlier\n * \"metadata hack\" approach.\n *\n * The new path is the FULL path including the filename — e.g.\n * `Reports/2026/Q1/forecast.pdf`. Just changing the leaf renames in\n * place; changing intermediate segments moves into a different folder.\n */\nexport async function renameFile(\n fileId: string,\n body: RenameFileRequest,\n opts: RequestOptions = {},\n): Promise<{ data: FileRecordApi; meta: ResponseMeta }> {\n return apiPost(\n buildPath(\"/files/{file_id}/rename\", { file_id: fileId }),\n body,\n withFileOrganization(fileId, opts),\n );\n}\n\n/**\n * Copy a file to a new logical path. Backend auto-creates parent folders.\n * The copy counts against the caller's storage + file-count quota; refused\n * uploads come back as 413 with the relevant quota code.\n *\n * `overwrite: true` replaces an existing file at `target_path`; the\n * default (false) returns `409 file_already_exists` on conflict.\n */\nexport async function copyFile(\n fileId: string,\n body: CopyFileRequest,\n opts: RequestOptions = {},\n): Promise<{ data: FileUploadResponse; meta: ResponseMeta }> {\n // Contract-bound: the copy 200 is `FileUploadResponse` (NOT `FileRecord` —\n // the pre-conversion assertion was wrong; see the module report). Deriving it\n // through the typed client fixes that lie and locks the shape to the contract.\n return apiPost(\n buildPath(\"/files/{file_id}/copy\", { file_id: fileId }),\n body,\n opts,\n );\n}\n"],"mappings":"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","names":[]}
@@ -0,0 +1,63 @@
1
+ /**
2
+ * features/files/api/folders.ts
3
+ *
4
+ * Folder CRUD against the Python REST backend. Listing also goes through
5
+ * REST here for parity with the contract; supabase-js reads remain the
6
+ * preferred path for the live tree (`get_user_file_tree`).
7
+ *
8
+ * Backend contract: features/files/cloud_files_frontend.md §6 (Folders)
9
+ * plus the Python team's P-6 (folder CRUD) deliverables.
10
+ */
11
+ import { type RequestOptions, type ResponseMeta } from "../host/python-client.js";
12
+ import type { components } from "@ai-matrx/agents/generated/api-types";
13
+ import type { BulkMoveFoldersRequest, BulkResponse, CloudFolderRow, CreateFolderRequest, FolderPatchRequest } from "../types.js";
14
+ export declare function listFolders(params?: {
15
+ parentPath?: string;
16
+ }, opts?: RequestOptions): Promise<{
17
+ data: CloudFolderRow[];
18
+ meta: ResponseMeta;
19
+ }>;
20
+ /**
21
+ * Create a folder. Prefer path-style (`folder_path: "Images/Chat"`) — the
22
+ * backend will create any missing intermediate folders atomically and
23
+ * idempotently, matching upload semantics.
24
+ */
25
+ export declare function createFolder(body: CreateFolderRequest, opts?: RequestOptions): Promise<{
26
+ data: CloudFolderRow;
27
+ meta: ResponseMeta;
28
+ }>;
29
+ /**
30
+ * Ensure a complete logical folder path in one idempotent backend operation.
31
+ * The server creates missing intermediate segments atomically; callers must
32
+ * never reproduce that traversal with one PostgREST lookup per segment.
33
+ */
34
+ export declare function ensureFolderPath(folderPath: string, visibility?: CreateFolderRequest["visibility"], opts?: RequestOptions): Promise<{
35
+ data: CloudFolderRow;
36
+ meta: ResponseMeta;
37
+ }>;
38
+ /**
39
+ * Update a folder — rename, move (via `parent_id`), or change visibility.
40
+ * The backend cascades `folder_path` updates to descendants on the server
41
+ * side; we don't replay that on the client.
42
+ */
43
+ export declare function patchFolder(folderId: string, body: FolderPatchRequest, opts?: RequestOptions): Promise<{
44
+ data: CloudFolderRow;
45
+ meta: ResponseMeta;
46
+ }>;
47
+ /**
48
+ * Move a folder to Trash (soft delete — restorable). The backend cascades to
49
+ * all descendants. There is no hard delete.
50
+ */
51
+ export declare function deleteFolder(folderId: string, opts?: RequestOptions): Promise<{
52
+ data: components["schemas"]["DeleteResponse"];
53
+ meta: ResponseMeta;
54
+ }>;
55
+ /**
56
+ * Move many folders to a new parent in one call. Returns a per-item
57
+ * succeeded/failed envelope so the caller can show per-row error chips
58
+ * without aborting the whole batch.
59
+ */
60
+ export declare function bulkMoveFolders(body: BulkMoveFoldersRequest, opts?: RequestOptions): Promise<{
61
+ data: BulkResponse;
62
+ meta: ResponseMeta;
63
+ }>;
@@ -0,0 +1,56 @@
1
+ import {
2
+ getJson,
3
+ patchJson,
4
+ postJson
5
+ } from "../host/python-client.js";
6
+ import { apiDelete, buildPath } from "../host/typed-client.js";
7
+ async function listFolders(params = {}, opts = {}) {
8
+ const q = params.parentPath ? `?parent_path=${encodeURIComponent(params.parentPath)}` : "";
9
+ return getJson(`/files/folders${q}`, opts);
10
+ }
11
+ async function createFolder(body, opts = {}) {
12
+ return postJson(
13
+ "/folders",
14
+ body,
15
+ opts
16
+ );
17
+ }
18
+ async function ensureFolderPath(folderPath, visibility = "personal", opts = {}) {
19
+ const normalizedPath = folderPath.split("/").map((segment) => segment.trim()).filter(Boolean).join("/");
20
+ if (!normalizedPath) {
21
+ throw new Error("folderPath cannot be empty.");
22
+ }
23
+ return createFolder(
24
+ { folder_path: normalizedPath, visibility, metadata: null },
25
+ opts
26
+ );
27
+ }
28
+ async function patchFolder(folderId, body, opts = {}) {
29
+ return patchJson(
30
+ `/folders/${folderId}`,
31
+ body,
32
+ opts
33
+ );
34
+ }
35
+ async function deleteFolder(folderId, opts = {}) {
36
+ return apiDelete(
37
+ buildPath("/folders/{folder_id}", { folder_id: folderId }),
38
+ opts
39
+ );
40
+ }
41
+ async function bulkMoveFolders(body, opts = {}) {
42
+ return postJson(
43
+ "/folders/bulk/move",
44
+ body,
45
+ opts
46
+ );
47
+ }
48
+ export {
49
+ bulkMoveFolders,
50
+ createFolder,
51
+ deleteFolder,
52
+ ensureFolderPath,
53
+ listFolders,
54
+ patchFolder
55
+ };
56
+ //# sourceMappingURL=folders.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../../../src/files/engine/api/folders.ts"],"sourcesContent":["/**\n * features/files/api/folders.ts\n *\n * Folder CRUD against the Python REST backend. Listing also goes through\n * REST here for parity with the contract; supabase-js reads remain the\n * preferred path for the live tree (`get_user_file_tree`).\n *\n * Backend contract: features/files/cloud_files_frontend.md §6 (Folders)\n * plus the Python team's P-6 (folder CRUD) deliverables.\n */\n\nimport {\n getJson,\n patchJson,\n postJson,\n type RequestOptions,\n type ResponseMeta,\n} from \"../host/python-client\";\nimport { apiDelete, buildPath } from \"../host/typed-client\";\nimport type { components } from \"@ai-matrx/agents/generated/api-types\";\nimport type {\n BulkMoveFoldersRequest,\n BulkResponse,\n CloudFolderRow,\n CreateFolderRequest,\n FolderPatchRequest,\n} from \"../types\";\n\n// ---------------------------------------------------------------------------\n// Read\n// ---------------------------------------------------------------------------\n\n// Left on the raw client: the contract types this response as an untyped\n// `{ [key: string]: unknown }[]` (operation `list_folders_files_folders_get`),\n// while consumers deref concrete `CloudFolderRow` (DB-row) fields. Binding to\n// the contract would replace the row shape with `unknown` and break them.\nexport async function listFolders(\n params: { parentPath?: string } = {},\n opts: RequestOptions = {},\n): Promise<{ data: CloudFolderRow[]; meta: ResponseMeta }> {\n const q = params.parentPath\n ? `?parent_path=${encodeURIComponent(params.parentPath)}`\n : \"\";\n return getJson<CloudFolderRow[]>(`/files/folders${q}`, opts);\n}\n\n// ---------------------------------------------------------------------------\n// Create / update / delete (Python P-6)\n// ---------------------------------------------------------------------------\n\n/**\n * Create a folder. Prefer path-style (`folder_path: \"Images/Chat\"`) — the\n * backend will create any missing intermediate folders atomically and\n * idempotently, matching upload semantics.\n */\n// Left on the raw client: two contract disagreements make binding unsafe here.\n// (1) The contract's `CreateFolderRequest` schema has ONLY `folder_path` (required),\n// `visibility`, `metadata` — it does NOT declare `folder_name` / `parent_id`,\n// which this FE type sends. (2) The contract response is `FolderRecord`\n// (owner_id / all-optional), not the DB-row `CloudFolderRow` (created_by /\n// organization_id / is_system) that consumers read. See report — latent bug.\nexport async function createFolder(\n body: CreateFolderRequest,\n opts: RequestOptions = {},\n): Promise<{ data: CloudFolderRow; meta: ResponseMeta }> {\n return postJson<CloudFolderRow, CreateFolderRequest>(\n \"/folders\",\n body,\n opts,\n );\n}\n\n/**\n * Ensure a complete logical folder path in one idempotent backend operation.\n * The server creates missing intermediate segments atomically; callers must\n * never reproduce that traversal with one PostgREST lookup per segment.\n */\nexport async function ensureFolderPath(\n folderPath: string,\n visibility: CreateFolderRequest[\"visibility\"] = \"personal\",\n opts: RequestOptions = {},\n): Promise<{ data: CloudFolderRow; meta: ResponseMeta }> {\n const normalizedPath = folderPath\n .split(\"/\")\n .map((segment) => segment.trim())\n .filter(Boolean)\n .join(\"/\");\n if (!normalizedPath) {\n throw new Error(\"folderPath cannot be empty.\");\n }\n\n return createFolder(\n { folder_path: normalizedPath, visibility, metadata: null },\n opts,\n );\n}\n\n/**\n * Update a folder — rename, move (via `parent_id`), or change visibility.\n * The backend cascades `folder_path` updates to descendants on the server\n * side; we don't replay that on the client.\n */\n// Left on the raw client: same disagreements as `createFolder`. The contract's\n// `PatchFolderRequest` has `folder_path` / `visibility` / `metadata`, while this\n// FE type sends `folder_name` / `parent_id`; and the contract response is\n// `FolderRecord`, not the DB-row `CloudFolderRow` consumers read.\nexport async function patchFolder(\n folderId: string,\n body: FolderPatchRequest,\n opts: RequestOptions = {},\n): Promise<{ data: CloudFolderRow; meta: ResponseMeta }> {\n return patchJson<CloudFolderRow, FolderPatchRequest>(\n `/folders/${folderId}`,\n body,\n opts,\n );\n}\n\n/**\n * Move a folder to Trash (soft delete — restorable). The backend cascades to\n * all descendants. There is no hard delete.\n */\nexport async function deleteFolder(\n folderId: string,\n opts: RequestOptions = {},\n): Promise<{ data: components[\"schemas\"][\"DeleteResponse\"]; meta: ResponseMeta }> {\n // Contract-bound: response derived from `delete_folder_folders__folder_id__delete`.\n return apiDelete(\n buildPath(\"/folders/{folder_id}\", { folder_id: folderId }),\n opts,\n );\n}\n\n// ---------------------------------------------------------------------------\n// Bulk (Python P-7)\n// ---------------------------------------------------------------------------\n\n/**\n * Move many folders to a new parent in one call. Returns a per-item\n * succeeded/failed envelope so the caller can show per-row error chips\n * without aborting the whole batch.\n */\n// Left on the raw client: the request is contract-compatible, but the contract's\n// `BulkResponse.results[].error` is OPTIONAL while this FE `BulkResponse` (and its\n// thunk consumer) treats `error` as REQUIRED (`string | null`). Routing would force\n// the looser optional type onto the consumer and break it. See report — latent bug.\nexport async function bulkMoveFolders(\n body: BulkMoveFoldersRequest,\n opts: RequestOptions = {},\n): Promise<{ data: BulkResponse; meta: ResponseMeta }> {\n return postJson<BulkResponse, BulkMoveFoldersRequest>(\n \"/folders/bulk/move\",\n body,\n opts,\n );\n}\n"],"mappings":"AAWA;AAAA,EACE;AAAA,EACA;AAAA,EACA;AAAA,OAGK;AACP,SAAS,WAAW,iBAAiB;AAkBrC,eAAsB,YACpB,SAAkC,CAAC,GACnC,OAAuB,CAAC,GACiC;AACzD,QAAM,IAAI,OAAO,aACb,gBAAgB,mBAAmB,OAAO,UAAU,CAAC,KACrD;AACJ,SAAO,QAA0B,iBAAiB,CAAC,IAAI,IAAI;AAC7D;AAiBA,eAAsB,aACpB,MACA,OAAuB,CAAC,GAC+B;AACvD,SAAO;AAAA,IACL;AAAA,IACA;AAAA,IACA;AAAA,EACF;AACF;AAOA,eAAsB,iBACpB,YACA,aAAgD,YAChD,OAAuB,CAAC,GAC+B;AACvD,QAAM,iBAAiB,WACpB,MAAM,GAAG,EACT,IAAI,CAAC,YAAY,QAAQ,KAAK,CAAC,EAC/B,OAAO,OAAO,EACd,KAAK,GAAG;AACX,MAAI,CAAC,gBAAgB;AACnB,UAAM,IAAI,MAAM,6BAA6B;AAAA,EAC/C;AAEA,SAAO;AAAA,IACL,EAAE,aAAa,gBAAgB,YAAY,UAAU,KAAK;AAAA,IAC1D;AAAA,EACF;AACF;AAWA,eAAsB,YACpB,UACA,MACA,OAAuB,CAAC,GAC+B;AACvD,SAAO;AAAA,IACL,YAAY,QAAQ;AAAA,IACpB;AAAA,IACA;AAAA,EACF;AACF;AAMA,eAAsB,aACpB,UACA,OAAuB,CAAC,GACwD;AAEhF,SAAO;AAAA,IACL,UAAU,wBAAwB,EAAE,WAAW,SAAS,CAAC;AAAA,IACzD;AAAA,EACF;AACF;AAeA,eAAsB,gBACpB,MACA,OAAuB,CAAC,GAC6B;AACrD,SAAO;AAAA,IACL;AAAA,IACA;AAAA,IACA;AAAA,EACF;AACF;","names":[]}