@ai-matrx/media 0.8.0 → 0.9.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +15 -0
- package/dist/files/engine/api/assets.d.ts +223 -0
- package/dist/files/engine/api/assets.js +124 -0
- package/dist/files/engine/api/assets.js.map +1 -0
- package/dist/files/engine/api/direct.d.ts +43 -0
- package/dist/files/engine/api/direct.js +73 -0
- package/dist/files/engine/api/direct.js.map +1 -0
- package/dist/files/engine/api/fileOrganization.d.ts +34 -0
- package/dist/files/engine/api/fileOrganization.js +38 -0
- package/dist/files/engine/api/fileOrganization.js.map +1 -0
- package/dist/files/engine/api/files.d.ts +249 -0
- package/dist/files/engine/api/files.js +231 -0
- package/dist/files/engine/api/files.js.map +1 -0
- package/dist/files/engine/api/folders.d.ts +63 -0
- package/dist/files/engine/api/folders.js +56 -0
- package/dist/files/engine/api/folders.js.map +1 -0
- package/dist/files/engine/api/office.d.ts +27 -0
- package/dist/files/engine/api/office.js +19 -0
- package/dist/files/engine/api/office.js.map +1 -0
- package/dist/files/engine/api/permissions.d.ts +37 -0
- package/dist/files/engine/api/permissions.js +109 -0
- package/dist/files/engine/api/permissions.js.map +1 -0
- package/dist/files/engine/api/versions.d.ts +26 -0
- package/dist/files/engine/api/versions.js +37 -0
- package/dist/files/engine/api/versions.js.map +1 -0
- package/dist/files/engine/cache/idb-store.d.ts +95 -0
- package/dist/files/engine/cache/idb-store.js +172 -0
- package/dist/files/engine/cache/idb-store.js.map +1 -0
- package/dist/files/engine/cache/policy.d.ts +14 -0
- package/dist/files/engine/cache/policy.js +40 -0
- package/dist/files/engine/cache/policy.js.map +1 -0
- package/dist/files/engine/cache/register-service-worker.d.ts +57 -0
- package/dist/files/engine/cache/register-service-worker.js +80 -0
- package/dist/files/engine/cache/register-service-worker.js.map +1 -0
- package/dist/files/engine/db-types.d.ts +48865 -0
- package/dist/files/engine/db-types.js +1 -0
- package/dist/files/engine/db-types.js.map +1 -0
- package/dist/files/engine/filesDb.d.ts +1913 -0
- package/dist/files/engine/filesDb.js +28 -0
- package/dist/files/engine/filesDb.js.map +1 -0
- package/dist/files/engine/handler/handler.d.ts +82 -0
- package/dist/files/engine/handler/handler.js +124 -0
- package/dist/files/engine/handler/handler.js.map +1 -0
- package/dist/files/engine/handler/hooks/useFile.d.ts +17 -0
- package/dist/files/engine/handler/hooks/useFile.js +75 -0
- package/dist/files/engine/handler/hooks/useFile.js.map +1 -0
- package/dist/files/engine/handler/hooks/useFileUpload.d.ts +76 -0
- package/dist/files/engine/handler/hooks/useFileUpload.js +67 -0
- package/dist/files/engine/handler/hooks/useFileUpload.js.map +1 -0
- package/dist/files/engine/handler/input/normalize.d.ts +14 -0
- package/dist/files/engine/handler/input/normalize.js +365 -0
- package/dist/files/engine/handler/input/normalize.js.map +1 -0
- package/dist/files/engine/handler/intelligence/access.d.ts +35 -0
- package/dist/files/engine/handler/intelligence/access.js +85 -0
- package/dist/files/engine/handler/intelligence/access.js.map +1 -0
- package/dist/files/engine/handler/intelligence/magic-bytes.d.ts +21 -0
- package/dist/files/engine/handler/intelligence/magic-bytes.js +67 -0
- package/dist/files/engine/handler/intelligence/magic-bytes.js.map +1 -0
- package/dist/files/engine/handler/output/target.d.ts +19 -0
- package/dist/files/engine/handler/output/target.js +222 -0
- package/dist/files/engine/handler/output/target.js.map +1 -0
- package/dist/files/engine/handler/resolver.d.ts +34 -0
- package/dist/files/engine/handler/resolver.js +133 -0
- package/dist/files/engine/handler/resolver.js.map +1 -0
- package/dist/files/engine/handler/types.d.ts +367 -0
- package/dist/files/engine/handler/types.js +1 -0
- package/dist/files/engine/handler/types.js.map +1 -0
- package/dist/files/engine/handler/upload.d.ts +32 -0
- package/dist/files/engine/handler/upload.js +295 -0
- package/dist/files/engine/handler/upload.js.map +1 -0
- package/dist/files/engine/handler/utils/classify.d.ts +14 -0
- package/dist/files/engine/handler/utils/classify.js +27 -0
- package/dist/files/engine/handler/utils/classify.js.map +1 -0
- package/dist/files/engine/handler/utils/prefer-locator.d.ts +32 -0
- package/dist/files/engine/handler/utils/prefer-locator.js +34 -0
- package/dist/files/engine/handler/utils/prefer-locator.js.map +1 -0
- package/dist/files/engine/handler/utils/python-base.d.ts +129 -0
- package/dist/files/engine/handler/utils/python-base.js +60 -0
- package/dist/files/engine/handler/utils/python-base.js.map +1 -0
- package/dist/files/engine/hooks/blob-cache.d.ts +130 -0
- package/dist/files/engine/hooks/blob-cache.js +162 -0
- package/dist/files/engine/hooks/blob-cache.js.map +1 -0
- package/dist/files/engine/hooks/office-extraction-cache.d.ts +28 -0
- package/dist/files/engine/hooks/office-extraction-cache.js +67 -0
- package/dist/files/engine/hooks/office-extraction-cache.js.map +1 -0
- package/dist/files/engine/host/configure.d.ts +166 -0
- package/dist/files/engine/host/configure.js +30 -0
- package/dist/files/engine/host/configure.js.map +1 -0
- package/dist/files/engine/host/org.d.ts +8 -0
- package/dist/files/engine/host/org.js +16 -0
- package/dist/files/engine/host/org.js.map +1 -0
- package/dist/files/engine/host/python-client.d.ts +60 -0
- package/dist/files/engine/host/python-client.js +46 -0
- package/dist/files/engine/host/python-client.js.map +1 -0
- package/dist/files/engine/host/share-links.d.ts +20 -0
- package/dist/files/engine/host/share-links.js +32 -0
- package/dist/files/engine/host/share-links.js.map +1 -0
- package/dist/files/engine/host/store.d.ts +24 -0
- package/dist/files/engine/host/store.js +23 -0
- package/dist/files/engine/host/store.js.map +1 -0
- package/dist/files/engine/host/supabase.d.ts +3808 -0
- package/dist/files/engine/host/supabase.js +33 -0
- package/dist/files/engine/host/supabase.js.map +1 -0
- package/dist/files/engine/host/toast.d.ts +14 -0
- package/dist/files/engine/host/toast.js +20 -0
- package/dist/files/engine/host/toast.js.map +1 -0
- package/dist/files/engine/host/typed-client.d.ts +136 -0
- package/dist/files/engine/host/typed-client.js +66 -0
- package/dist/files/engine/host/typed-client.js.map +1 -0
- package/dist/files/engine/index.d.ts +22 -0
- package/dist/files/engine/index.js +49 -0
- package/dist/files/engine/index.js.map +1 -0
- package/dist/files/engine/media/our-file-sources.d.ts +39 -0
- package/dist/files/engine/media/our-file-sources.js +83 -0
- package/dist/files/engine/media/our-file-sources.js.map +1 -0
- package/dist/files/engine/media/signed-url.d.ts +1 -0
- package/dist/files/engine/media/signed-url.js +6 -0
- package/dist/files/engine/media/signed-url.js.map +1 -0
- package/dist/files/engine/redux/converters.d.ts +73 -0
- package/dist/files/engine/redux/converters.js +286 -0
- package/dist/files/engine/redux/converters.js.map +1 -0
- package/dist/files/engine/redux/file-hydration.d.ts +15 -0
- package/dist/files/engine/redux/file-hydration.js +46 -0
- package/dist/files/engine/redux/file-hydration.js.map +1 -0
- package/dist/files/engine/redux/file-tree-auth-boundary.d.ts +5 -0
- package/dist/files/engine/redux/file-tree-auth-boundary.js +28 -0
- package/dist/files/engine/redux/file-tree-auth-boundary.js.map +1 -0
- package/dist/files/engine/redux/file-tree-timeout.d.ts +32 -0
- package/dist/files/engine/redux/file-tree-timeout.js +73 -0
- package/dist/files/engine/redux/file-tree-timeout.js.map +1 -0
- package/dist/files/engine/redux/mutation-toast-middleware.d.ts +27 -0
- package/dist/files/engine/redux/mutation-toast-middleware.js +103 -0
- package/dist/files/engine/redux/mutation-toast-middleware.js.map +1 -0
- package/dist/files/engine/redux/realtime-middleware.d.ts +61 -0
- package/dist/files/engine/redux/realtime-middleware.js +431 -0
- package/dist/files/engine/redux/realtime-middleware.js.map +1 -0
- package/dist/files/engine/redux/request-ledger.d.ts +68 -0
- package/dist/files/engine/redux/request-ledger.js +66 -0
- package/dist/files/engine/redux/request-ledger.js.map +1 -0
- package/dist/files/engine/redux/selectors.d.ts +3254 -0
- package/dist/files/engine/redux/selectors.js +428 -0
- package/dist/files/engine/redux/selectors.js.map +1 -0
- package/dist/files/engine/redux/slice.d.ts +157 -0
- package/dist/files/engine/redux/slice.js +836 -0
- package/dist/files/engine/redux/slice.js.map +1 -0
- package/dist/files/engine/redux/thunks.d.ts +449 -0
- package/dist/files/engine/redux/thunks.js +1684 -0
- package/dist/files/engine/redux/thunks.js.map +1 -0
- package/dist/files/engine/redux/tree-utils.d.ts +90 -0
- package/dist/files/engine/redux/tree-utils.js +200 -0
- package/dist/files/engine/redux/tree-utils.js.map +1 -0
- package/dist/files/engine/support/claimsUser.d.ts +77 -0
- package/dist/files/engine/support/claimsUser.js +48 -0
- package/dist/files/engine/support/claimsUser.js.map +1 -0
- package/dist/files/engine/support/datetime.d.ts +6 -0
- package/dist/files/engine/support/datetime.js +15 -0
- package/dist/files/engine/support/datetime.js.map +1 -0
- package/dist/files/engine/support/document-visibility.d.ts +14 -0
- package/dist/files/engine/support/document-visibility.js +28 -0
- package/dist/files/engine/support/document-visibility.js.map +1 -0
- package/dist/files/engine/support/logger.d.ts +28 -0
- package/dist/files/engine/support/logger.js +36 -0
- package/dist/files/engine/support/logger.js.map +1 -0
- package/dist/files/engine/types.d.ts +1081 -0
- package/dist/files/engine/types.js +30 -0
- package/dist/files/engine/types.js.map +1 -0
- package/dist/files/engine/upload/cloudUpload.d.ts +182 -0
- package/dist/files/engine/upload/cloudUpload.js +418 -0
- package/dist/files/engine/upload/cloudUpload.js.map +1 -0
- package/dist/files/engine/upload/tusUpload.d.ts +100 -0
- package/dist/files/engine/upload/tusUpload.js +258 -0
- package/dist/files/engine/upload/tusUpload.js.map +1 -0
- package/dist/files/engine/upload/uploadDedupGuard.d.ts +58 -0
- package/dist/files/engine/upload/uploadDedupGuard.js +41 -0
- package/dist/files/engine/upload/uploadDedupGuard.js.map +1 -0
- package/dist/files/engine/upload/uploadGuardOpeners.d.ts +70 -0
- package/dist/files/engine/upload/uploadGuardOpeners.js +54 -0
- package/dist/files/engine/upload/uploadGuardOpeners.js.map +1 -0
- package/dist/files/engine/utils/file-types.d.ts +243 -0
- package/dist/files/engine/utils/file-types.js +1722 -0
- package/dist/files/engine/utils/file-types.js.map +1 -0
- package/dist/files/engine/utils/user-visible.d.ts +133 -0
- package/dist/files/engine/utils/user-visible.js +115 -0
- package/dist/files/engine/utils/user-visible.js.map +1 -0
- package/package.json +393 -9
|
@@ -0,0 +1,1081 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* features/files/types.ts
|
|
3
|
+
*
|
|
4
|
+
* Single source of truth for all cloud-files types. Import from
|
|
5
|
+
* `@/features/files/types` — never duplicate types in feature subfolders.
|
|
6
|
+
*
|
|
7
|
+
* LAYERS
|
|
8
|
+
* ------
|
|
9
|
+
* 1. Domain types — camelCase, what components and Redux work with.
|
|
10
|
+
* 2. DB row types — snake_case, derived from Supabase-generated `Database`.
|
|
11
|
+
* 3. API types — from Python OpenAPI-generated `components["schemas"]`.
|
|
12
|
+
* 4. Runtime records — domain types + dirty/loading/error metadata for Redux.
|
|
13
|
+
* 5. Tree & UI types — normalized structure + UI state shapes.
|
|
14
|
+
* 6. Upload types — upload orchestrator state.
|
|
15
|
+
* 7. Error types — re-export of BackendApiError, plus files-specific codes.
|
|
16
|
+
*
|
|
17
|
+
* Do NOT duplicate or re-declare any type from here in feature subfolders.
|
|
18
|
+
*/
|
|
19
|
+
import type { components } from "@ai-matrx/agents/generated/api-types";
|
|
20
|
+
import type { Database } from "./db-types.js";
|
|
21
|
+
import type { FieldFlags } from "@ai-matrx/agents/field-flags";
|
|
22
|
+
import type { readFileRowById } from "./filesDb.js";
|
|
23
|
+
/**
|
|
24
|
+
* THE canonical `platform.visibility` enum, in enum order:
|
|
25
|
+
* `personal < internal < link < public`. Identical on the server
|
|
26
|
+
* (`matrx_utils.visibility.VisibilityLiteral`) and in the DB. One vocabulary,
|
|
27
|
+
* no per-domain dialect.
|
|
28
|
+
*
|
|
29
|
+
* Two bugs are fossilized here; do not reintroduce either.
|
|
30
|
+
*
|
|
31
|
+
* 1. `internal` was folded into `personal` on read until 2026-07-26, so a file
|
|
32
|
+
* readable by an entire organization was labelled "Only you" everywhere.
|
|
33
|
+
* Never collapse a level to shrink this union — they are different facts.
|
|
34
|
+
*
|
|
35
|
+
* 2. This domain used to call `link` "shared". That is NOT a synonym: `shared`
|
|
36
|
+
* was RETIRED from the DB enum on 2026-07-21, and the server's legacy map
|
|
37
|
+
* reconciles it to `personal`. So the old code read `link` as `"shared"`
|
|
38
|
+
* and, on any write-back, silently DOWNGRADED the file to `personal`.
|
|
39
|
+
* Never send `shared` or `private` to the server.
|
|
40
|
+
*/
|
|
41
|
+
import type { Visibility, PermissionLevel, MediaRef, FileIdentityHint } from "../../files.js";
|
|
42
|
+
export type { Visibility, PermissionLevel, MediaRef, FileIdentityHint };
|
|
43
|
+
export type ResourceType = "file" | "folder";
|
|
44
|
+
/**
|
|
45
|
+
* Who a grant targets. Mirrors the canonical `iam.permissions` three-way
|
|
46
|
+
* mutual exclusion (CHECK `user_or_org_or_public`): exactly one of a user, an
|
|
47
|
+
* organization, or the public sentinel per row.
|
|
48
|
+
* - `"user"` — `granted_to_user_id` set; `granteeId` is that user id.
|
|
49
|
+
* - `"group"` — `granted_to_organization_id` set; `granteeId` is that org id.
|
|
50
|
+
* - `"public"` — `is_public = true`; there is NO grantee id, so `granteeId`
|
|
51
|
+
* carries the permission row's own id (never `""`). A public
|
|
52
|
+
* grant is "anyone with access", not a member — member/avatar
|
|
53
|
+
* UIs must exclude it, and it is revoked by flipping the
|
|
54
|
+
* resource's visibility (see `features/sharing/FEATURE.md`),
|
|
55
|
+
* not via the by-grantee-id REST endpoint.
|
|
56
|
+
*/
|
|
57
|
+
export type GranteeType = "user" | "group" | "public";
|
|
58
|
+
/**
|
|
59
|
+
* Metadata a caller may already know when all it has is a durable file id.
|
|
60
|
+
* These values seed the canonical Redux record before field hydration runs;
|
|
61
|
+
* omitted keys remain genuinely unloaded and are fetched on demand.
|
|
62
|
+
*/
|
|
63
|
+
type FilesTables = Database["files"]["Tables"];
|
|
64
|
+
type IamTables = Database["iam"]["Tables"];
|
|
65
|
+
export type CloudFileRow = FilesTables["files"]["Row"];
|
|
66
|
+
export type CloudFileInsert = FilesTables["files"]["Insert"];
|
|
67
|
+
export type CloudFileUpdate = FilesTables["files"]["Update"];
|
|
68
|
+
/**
|
|
69
|
+
* What the client is actually ALLOWED to read from `files.files` — DERIVED from
|
|
70
|
+
* `FILES_TABLE_COLUMNS` (the ONE canonical select, in [filesDb.ts](./filesDb.ts))
|
|
71
|
+
* rather than declared beside it, so the two can never disagree.
|
|
72
|
+
*
|
|
73
|
+
* `files.files` grants `authenticated` NO table-level SELECT: every readable
|
|
74
|
+
* column carries its own column grant, which is how the server-only native
|
|
75
|
+
* storage location stays server-only. So a column ADDED to the table is
|
|
76
|
+
* readable by nobody until a migration grants it, and a column list that names
|
|
77
|
+
* an ungranted column fails the WHOLE read with "permission denied for table
|
|
78
|
+
* files". Subtracting one name from the generated Row type therefore described
|
|
79
|
+
* a row the client cannot actually read: on 2026-09-13 the table gained
|
|
80
|
+
* `origin_device_id` and `client_modified_at` (folder-sync 028, no client
|
|
81
|
+
* grant then) and every selected row stopped matching this type. Deriving from
|
|
82
|
+
* the query makes the select list the single truth. (2026-09-24: `authenticated`
|
|
83
|
+
* now holds SELECT on `origin_device_id` and `artifact_kind`, verified live, and
|
|
84
|
+
* both are in the select list — Recents needs the first.)
|
|
85
|
+
*
|
|
86
|
+
* Same deal for `file_versions` via `FILE_VERSIONS_TABLE_COLUMNS`.
|
|
87
|
+
*/
|
|
88
|
+
export type CloudFileReadRow = NonNullable<Awaited<ReturnType<typeof readFileRowById>>>;
|
|
89
|
+
export type CloudFileVersionReadRow = Omit<FilesTables["file_versions"]["Row"], "storage_uri" | "custom_fields">;
|
|
90
|
+
export type CloudFolderRow = FilesTables["folders"]["Row"];
|
|
91
|
+
export type CloudFolderInsert = FilesTables["folders"]["Insert"];
|
|
92
|
+
export type CloudFolderUpdate = FilesTables["folders"]["Update"];
|
|
93
|
+
export type CloudFileVersionRow = FilesTables["file_versions"]["Row"];
|
|
94
|
+
/**
|
|
95
|
+
* File-permission grants live in the CANONICAL grant store `iam.permissions`
|
|
96
|
+
* (resource_type='file'), NOT in the legacy cld_ file-permission duplicate
|
|
97
|
+
* (deprecated in the 2026 DB cutover — see docs/db_rebuild/03-app-agent-cutover-instructions.md §1a).
|
|
98
|
+
*/
|
|
99
|
+
export type CloudFilePermissionRow = IamTables["permissions"]["Row"];
|
|
100
|
+
/**
|
|
101
|
+
* Summary JSON returned by `iam.fn_list_resource_permissions` and
|
|
102
|
+
* `iam.fn_grant_resource_permission` — NOT a full `iam.permissions` row.
|
|
103
|
+
* Maps grantee/org columns into `grantee_id` + `grantee_type` and file-level
|
|
104
|
+
* `read|write|admin` permission strings.
|
|
105
|
+
*/
|
|
106
|
+
export interface IamResourcePermissionRpcRow {
|
|
107
|
+
resource_id: string;
|
|
108
|
+
resource_type: string;
|
|
109
|
+
grantee_id: string;
|
|
110
|
+
grantee_type: string;
|
|
111
|
+
permission_level: string;
|
|
112
|
+
granted_by: string | null;
|
|
113
|
+
expires_at: string | null;
|
|
114
|
+
}
|
|
115
|
+
export type FileRecordApi = components["schemas"]["FileRecord"];
|
|
116
|
+
export type FileUploadResponse = components["schemas"]["FileUploadResponse"];
|
|
117
|
+
export type FilePatchRequest = components["schemas"]["FilePatchRequest"];
|
|
118
|
+
export type GrantPermissionRequest = components["schemas"]["GrantPermissionRequest"];
|
|
119
|
+
/**
|
|
120
|
+
* Discriminator on every cloud-file record. Real records are bytes in S3;
|
|
121
|
+
* virtual records are Postgres rows surfaced by a `VirtualSourceAdapter`
|
|
122
|
+
* (Notes, Agent Apps, Tool UIs, code-files snippets, etc.) — see
|
|
123
|
+
* [features/files/virtual-sources/types.ts](./virtual-sources/types.ts).
|
|
124
|
+
*
|
|
125
|
+
* The `source` field defaults to `{ kind: "real" }` everywhere so existing
|
|
126
|
+
* callers compile unchanged. Synthetic ids of shape
|
|
127
|
+
* `vfs:<adapterId>:<virtualId>[:<fieldId>]` keep the cloud-files Redux
|
|
128
|
+
* `filesById` / `foldersById` maps a single keyspace.
|
|
129
|
+
*/
|
|
130
|
+
export type FileSource = {
|
|
131
|
+
kind: "real";
|
|
132
|
+
} | {
|
|
133
|
+
kind: "virtual";
|
|
134
|
+
adapterId: string;
|
|
135
|
+
virtualId: string;
|
|
136
|
+
fieldId?: string;
|
|
137
|
+
};
|
|
138
|
+
export interface CloudFile {
|
|
139
|
+
id: string;
|
|
140
|
+
ownerId: string;
|
|
141
|
+
/** Canonical owning workspace from `files.files.organization_id`. */
|
|
142
|
+
organizationId?: string | null;
|
|
143
|
+
filePath: string;
|
|
144
|
+
fileName: string;
|
|
145
|
+
mimeType: string | null;
|
|
146
|
+
fileSize: number | null;
|
|
147
|
+
checksum: string | null;
|
|
148
|
+
visibility: Visibility;
|
|
149
|
+
currentVersion: number;
|
|
150
|
+
parentFolderId: string | null;
|
|
151
|
+
metadata: Record<string, unknown>;
|
|
152
|
+
createdAt: string;
|
|
153
|
+
updatedAt: string;
|
|
154
|
+
deletedAt: string | null;
|
|
155
|
+
/**
|
|
156
|
+
* Permanent CDN URL (Cloudflare-fronted) when the file is public AND
|
|
157
|
+
* the server has the CDN feature enabled. ``null`` otherwise — callers
|
|
158
|
+
* should fall back to ``useFileSrc({ kind: "file_id", fileId })`` for the durable URL.
|
|
159
|
+
*
|
|
160
|
+
* Carries a ``?v=<checksum[:8]>`` cache-buster so a content change
|
|
161
|
+
* invalidates the cache instantly. **Do not strip the query string.**
|
|
162
|
+
*
|
|
163
|
+
* Populated by the API converter (``apiFileRecordToCloudFile``);
|
|
164
|
+
* always ``null`` for rows that came in via the direct DB read path
|
|
165
|
+
* because the DB has no ``public_url`` column — it's computed
|
|
166
|
+
* server-side from visibility + storage location + checksum. For DB-sourced
|
|
167
|
+
* rows, fall back to ``useFileSrc({ kind: "file_id", fileId })`` to fetch the canonical
|
|
168
|
+
* URL (which the server returns as a CDN URL when applicable).
|
|
169
|
+
*/
|
|
170
|
+
publicUrl: string | null;
|
|
171
|
+
/**
|
|
172
|
+
* The DURABLE URL envelope the REST `FileRecord` carries. `url` is the
|
|
173
|
+
* server's canonical always-renderable pick (CDN for public, the durable
|
|
174
|
+
* `/files/{id}/download?inline=1` route for private — authenticated by
|
|
175
|
+
* the `mx_files_session` cookie). `cdnUrl` is public-only and permanent.
|
|
176
|
+
* `downloadUrl` carries attachment disposition. None of them expire.
|
|
177
|
+
*
|
|
178
|
+
* These are populated by `apiFileRecordToCloudFile` from the REST
|
|
179
|
+
* response. They are `null` on the direct-DB read path (the `cld_files`
|
|
180
|
+
* table has no computed-URL columns) — DB-sourced rows build the durable
|
|
181
|
+
* URL from the file id via the resolver.
|
|
182
|
+
*/
|
|
183
|
+
url: string | null;
|
|
184
|
+
cdnUrl: string | null;
|
|
185
|
+
downloadUrl: string | null;
|
|
186
|
+
/**
|
|
187
|
+
* Backend-rendered thumbnail URL (Phase 1b universal thumbnails). Set
|
|
188
|
+
* for **every** uploaded file regardless of MIME — Python now renders
|
|
189
|
+
* SOCIAL_BASELINE variants (og_url / thumbnail_url / tiny_url) for
|
|
190
|
+
* images, PDFs (page 1), videos (10%-mark frame), audio (waveform),
|
|
191
|
+
* and even archives / text / unknown mimes (mime-family icon PNGs).
|
|
192
|
+
*
|
|
193
|
+
* Populated by `apiFileRecordToCloudFile` from `FileRecord.thumbnail_url`
|
|
194
|
+
* — the REST response field. The server resolves this from the
|
|
195
|
+
* variants store at request time (post-Phase-1b the legacy
|
|
196
|
+
* `cld_files.thumbnail_url` column is dropped).
|
|
197
|
+
*
|
|
198
|
+
* `null` for rows coming in via the direct Supabase read path — the
|
|
199
|
+
* row doesn't carry resolved URLs. Those callers should fall back to
|
|
200
|
+
* `useFileAsset(fileId)` and read `asset.variants["thumbnail_url"].url`,
|
|
201
|
+
* or `MediaThumbnail` will fall back to the category icon.
|
|
202
|
+
*
|
|
203
|
+
* Phase 1c: PDFs additionally get `Asset.variants["page1_url"]`
|
|
204
|
+
* (page 1 at 150 DPI ~1200×1700) for full-page detail views, and
|
|
205
|
+
* videos get `Asset.variants["poster_url"]` (native-res frame) for
|
|
206
|
+
* the HTML5 `<video poster>` attribute. Those are separate from this
|
|
207
|
+
* `thumbnailUrl` field and require the asset fetch.
|
|
208
|
+
*/
|
|
209
|
+
thumbnailUrl: string | null;
|
|
210
|
+
/** Real S3-backed bytes vs. virtual Postgres-backed adapter row. */
|
|
211
|
+
source: FileSource;
|
|
212
|
+
/**
|
|
213
|
+
* Binary lineage — points to the cld_files row this one was derived
|
|
214
|
+
* from (e.g. "extracted text from this PDF" or "page range 5–10 of
|
|
215
|
+
* the parent PDF"). Set by Phase 4A migration `0006_cld_files_lineage`.
|
|
216
|
+
* Null when the file was uploaded directly with no derivation.
|
|
217
|
+
* Optional on the FE because it is null for nearly every existing
|
|
218
|
+
* file and was added to the API after the initial schema landed.
|
|
219
|
+
*/
|
|
220
|
+
parentFileId?: string | null;
|
|
221
|
+
/**
|
|
222
|
+
* Free-form classifier set by the deriving system: "pdf_text_extract",
|
|
223
|
+
* "page_range_5_10", "ocr_re_run", "merge", … Used by lineage chips
|
|
224
|
+
* to label *how* the parent relates to this file.
|
|
225
|
+
*/
|
|
226
|
+
derivationKind?: string | null;
|
|
227
|
+
derivationMetadata?: Record<string, unknown> | null;
|
|
228
|
+
/**
|
|
229
|
+
* The registered device (`public.app_instances.id`) that wrote this row — a
|
|
230
|
+
* desktop sync client — or null for an in-app write. A device's write is the
|
|
231
|
+
* person's file but never their recent activity: Recents keys on this via
|
|
232
|
+
* `isRecentActivityFile` (mirror of `files.is_recent_activity`).
|
|
233
|
+
*/
|
|
234
|
+
originDeviceId?: string | null;
|
|
235
|
+
/**
|
|
236
|
+
* When set, this row is a deliberate parallel copy of `duplicateOfFileId`
|
|
237
|
+
* (the keeper). Set by:
|
|
238
|
+
* - the dedup consolidation script (soft-deletes the duplicate row +
|
|
239
|
+
* stamps the keeper's id here so refs to the dup still resolve), or
|
|
240
|
+
* - `intent: "force_new_copy"` uploads (user explicitly asked for a
|
|
241
|
+
* second copy of identical content).
|
|
242
|
+
* Null for every freshly-uploaded file.
|
|
243
|
+
*
|
|
244
|
+
* UI: surface a "duplicate of <keeper>" chip on rows where this is set.
|
|
245
|
+
* Refs that hit a `deletedAt != null` row should `useFile(duplicateOfFileId)`
|
|
246
|
+
* to follow the chain to the live keeper.
|
|
247
|
+
*/
|
|
248
|
+
duplicateOfFileId?: string | null;
|
|
249
|
+
/**
|
|
250
|
+
* Points at the "official" `processed_documents.id` for this file —
|
|
251
|
+
* the canonical text-extraction row out of potentially many re_extract /
|
|
252
|
+
* re_clean variants. UIs that show "extracted text" or feed Knowledge should
|
|
253
|
+
* use this column to find THE extract, not the freshest one.
|
|
254
|
+
*/
|
|
255
|
+
canonicalProcessedDocumentId?: string | null;
|
|
256
|
+
}
|
|
257
|
+
export interface CloudFolder {
|
|
258
|
+
id: string;
|
|
259
|
+
ownerId: string;
|
|
260
|
+
folderPath: string;
|
|
261
|
+
folderName: string;
|
|
262
|
+
parentId: string | null;
|
|
263
|
+
visibility: Visibility;
|
|
264
|
+
metadata: Record<string, unknown>;
|
|
265
|
+
createdAt: string;
|
|
266
|
+
updatedAt: string;
|
|
267
|
+
deletedAt: string | null;
|
|
268
|
+
/** Real cloud-folder vs. virtual adapter root / nested adapter folder. */
|
|
269
|
+
source: FileSource;
|
|
270
|
+
}
|
|
271
|
+
export interface CloudFileVersion {
|
|
272
|
+
id: string;
|
|
273
|
+
fileId: string;
|
|
274
|
+
versionNumber: number;
|
|
275
|
+
fileSize: number | null;
|
|
276
|
+
checksum: string | null;
|
|
277
|
+
createdBy: string | null;
|
|
278
|
+
createdAt: string;
|
|
279
|
+
changeSummary: string | null;
|
|
280
|
+
}
|
|
281
|
+
export interface CloudFilePermission {
|
|
282
|
+
id: string;
|
|
283
|
+
resourceId: string;
|
|
284
|
+
resourceType: ResourceType;
|
|
285
|
+
granteeId: string;
|
|
286
|
+
granteeType: GranteeType;
|
|
287
|
+
permissionLevel: PermissionLevel;
|
|
288
|
+
grantedBy: string | null;
|
|
289
|
+
grantedAt: string;
|
|
290
|
+
expiresAt: string | null;
|
|
291
|
+
}
|
|
292
|
+
/**
|
|
293
|
+
* A canonical share link (`platform.share_links`) scoped to a file or folder.
|
|
294
|
+
* Minted/listed/revoked via the canonical RPC family (`create_share_link` /
|
|
295
|
+
* `list_share_links` / `revoke_share_link` — see `utils/permissions/shareLinks.ts`).
|
|
296
|
+
*/
|
|
297
|
+
export interface CloudShareLink {
|
|
298
|
+
id: string;
|
|
299
|
+
resourceId: string;
|
|
300
|
+
resourceType: ResourceType;
|
|
301
|
+
shareToken: string;
|
|
302
|
+
permissionLevel: "viewer" | "editor";
|
|
303
|
+
label: string | null;
|
|
304
|
+
createdAt: string | null;
|
|
305
|
+
expiresAt: string | null;
|
|
306
|
+
maxUses: number | null;
|
|
307
|
+
useCount: number;
|
|
308
|
+
isActive: boolean;
|
|
309
|
+
}
|
|
310
|
+
export interface CloudTreeFileRow {
|
|
311
|
+
kind: "file";
|
|
312
|
+
id: string;
|
|
313
|
+
file_path: string;
|
|
314
|
+
file_name: string;
|
|
315
|
+
parent_folder_id: string | null;
|
|
316
|
+
mime_type: string | null;
|
|
317
|
+
/**
|
|
318
|
+
* File size in bytes. Renamed from `file_size` in Phase 0 (see
|
|
319
|
+
* docs/PYTHON_UPDATES.md §3). The RPC `tree_for_owner` (and friends)
|
|
320
|
+
* now returns `size_bytes`; converters read both names defensively
|
|
321
|
+
* during the transition. Consumers should always read `size_bytes`.
|
|
322
|
+
*/
|
|
323
|
+
size_bytes: number | null;
|
|
324
|
+
visibility: Visibility;
|
|
325
|
+
current_version: number;
|
|
326
|
+
effective_permission: PermissionLevel | null;
|
|
327
|
+
owner_id: string;
|
|
328
|
+
created_at: string;
|
|
329
|
+
updated_at: string;
|
|
330
|
+
deleted_at: string | null;
|
|
331
|
+
/** Device that wrote the row (desktop sync), null for an in-app write. */
|
|
332
|
+
origin_device_id: string | null;
|
|
333
|
+
/**
|
|
334
|
+
* The row's metadata as the RPC returns it — carries `system_artifact`, the
|
|
335
|
+
* marker every file list reads through `isListedFile` (2026-09-29; the tree
|
|
336
|
+
* used to drop it, so system files could not be told apart in the store).
|
|
337
|
+
*/
|
|
338
|
+
metadata: Record<string, unknown>;
|
|
339
|
+
}
|
|
340
|
+
export interface CloudTreeFolderRow {
|
|
341
|
+
kind: "folder";
|
|
342
|
+
id: string;
|
|
343
|
+
folder_path: string;
|
|
344
|
+
folder_name: string;
|
|
345
|
+
parent_id: string | null;
|
|
346
|
+
visibility: Visibility;
|
|
347
|
+
effective_permission: PermissionLevel | null;
|
|
348
|
+
owner_id: string;
|
|
349
|
+
created_at: string;
|
|
350
|
+
updated_at: string;
|
|
351
|
+
deleted_at: string | null;
|
|
352
|
+
metadata: Record<string, unknown>;
|
|
353
|
+
}
|
|
354
|
+
export type CloudTreeRow = CloudTreeFileRow | CloudTreeFolderRow;
|
|
355
|
+
export interface RuntimeMetadata<K extends string> {
|
|
356
|
+
_dirty: boolean;
|
|
357
|
+
_dirtyFields: FieldFlags<K>;
|
|
358
|
+
_loadedFields: FieldFlags<K>;
|
|
359
|
+
_loading: boolean;
|
|
360
|
+
_error: string | null;
|
|
361
|
+
_pendingRequestIds: string[];
|
|
362
|
+
}
|
|
363
|
+
export type CloudFileFieldSnapshot = Partial<Pick<CloudFile, "fileName" | "filePath" | "visibility" | "parentFolderId" | "metadata" | "deletedAt">>;
|
|
364
|
+
export interface CloudFileRecord extends CloudFile, RuntimeMetadata<keyof CloudFile> {
|
|
365
|
+
_fieldHistory: CloudFileFieldSnapshot;
|
|
366
|
+
}
|
|
367
|
+
export type CloudFolderFieldSnapshot = Partial<Pick<CloudFolder, "folderName" | "folderPath" | "parentId" | "visibility" | "metadata">>;
|
|
368
|
+
export interface CloudFolderRecord extends CloudFolder, RuntimeMetadata<keyof CloudFolder> {
|
|
369
|
+
_fieldHistory: CloudFolderFieldSnapshot;
|
|
370
|
+
}
|
|
371
|
+
export interface TreeChildren {
|
|
372
|
+
folderIds: string[];
|
|
373
|
+
fileIds: string[];
|
|
374
|
+
}
|
|
375
|
+
export interface TreeState {
|
|
376
|
+
rootFolderIds: string[];
|
|
377
|
+
rootFileIds: string[];
|
|
378
|
+
childrenByFolderId: Record<string, TreeChildren>;
|
|
379
|
+
fullyLoadedFolderIds: Record<string, true>;
|
|
380
|
+
status: "idle" | "loading" | "loaded" | "error";
|
|
381
|
+
error: string | null;
|
|
382
|
+
lastReconciledAt: number | null;
|
|
383
|
+
}
|
|
384
|
+
export type ViewMode = "list" | "grid" | "columns";
|
|
385
|
+
/**
|
|
386
|
+
* Sticky filter chips above the file table. Mirrors the `FilterChips`
|
|
387
|
+
* component's union — re-declared here (not imported) so the slice
|
|
388
|
+
* stays component-cycle-free. Keep in sync with
|
|
389
|
+
* `features/files/components/surfaces/desktop/FilterChips.tsx`.
|
|
390
|
+
*/
|
|
391
|
+
export type ChipFilter = "recents" | "starred";
|
|
392
|
+
/**
|
|
393
|
+
* Column sort keys. Mirror the file-table columns so users can sort by any
|
|
394
|
+
* column they're looking at. Folders always group ahead of files regardless
|
|
395
|
+
* of the active key (Box / Drive / Dropbox convention) — see `compareNodes`.
|
|
396
|
+
*/
|
|
397
|
+
export type SortBy = "name" | "type" | "extension" | "mime" | "path" | "owner" | "size" | "version" | "updated_at" | "created_at";
|
|
398
|
+
export type SortDirection = "asc" | "desc";
|
|
399
|
+
/** Whether the file table shows files only, folders only, or both. */
|
|
400
|
+
export type KindFilter = "all" | "files" | "folders";
|
|
401
|
+
/** Optional details surface (extension, mime, dimensions, etc.) on rows. */
|
|
402
|
+
export type DetailsLevel = "compact" | "extended";
|
|
403
|
+
/** Modified-date preset filter — "today", "week" = last 7d, "month" = last 30d. */
|
|
404
|
+
export type ModifiedFilter = "any" | "today" | "week" | "month";
|
|
405
|
+
/** Size preset filter — buckets familiar to users. */
|
|
406
|
+
export type SizeFilter = "any" | "small" | "medium" | "large" | "huge";
|
|
407
|
+
/**
|
|
408
|
+
* Access (visibility) filter. One option per `Visibility` level plus "any" —
|
|
409
|
+
* a level with no option is a level whose rows can never be filtered to.
|
|
410
|
+
*/
|
|
411
|
+
export type AccessFilter = "any" | Visibility;
|
|
412
|
+
/**
|
|
413
|
+
* Type filter — multi-select set of file categories (CODE, DOCUMENT, IMAGE,
|
|
414
|
+
* VIDEO, …). Empty array = "any type". Modeled as `string[]` (not the
|
|
415
|
+
* `FileCategory` enum from `utils/file-types.ts`) to keep the slice type
|
|
416
|
+
* import-cycle-free; the selectors / pickers cast at the boundary.
|
|
417
|
+
*/
|
|
418
|
+
export type TypeFilter = string[];
|
|
419
|
+
/** Owner filter — multi-select set of owner user ids. Empty = "any owner". */
|
|
420
|
+
export type OwnerFilter = string[];
|
|
421
|
+
/**
|
|
422
|
+
* Per-file Knowledge indexing status.
|
|
423
|
+
*
|
|
424
|
+
* - `indexed` — backend has a `processed_documents` row for this file
|
|
425
|
+
* - `not_indexed` — file exists but no doc row (`/files/{id}/document` 404)
|
|
426
|
+
* - `pending` — request is in flight
|
|
427
|
+
* - `unknown` — endpoint returned a transient error or hasn't been
|
|
428
|
+
* called yet for this file
|
|
429
|
+
*
|
|
430
|
+
* The pending state matters because the user toggles "Show Knowledge status"
|
|
431
|
+
* across hundreds of files at once — the column needs to show "Checking…"
|
|
432
|
+
* for the rows still in flight rather than flickering "Not indexed".
|
|
433
|
+
*/
|
|
434
|
+
export type RagStatus = "indexed" | "not_indexed" | "pending" | "unknown";
|
|
435
|
+
/**
|
|
436
|
+
* Knowledge filter — multi-select of statuses. Empty array = "any status".
|
|
437
|
+
* Modeled as `string[]` (not `RagStatus[]`) to mirror `TypeFilter` /
|
|
438
|
+
* `OwnerFilter` and keep the slice type import-cycle-free.
|
|
439
|
+
*/
|
|
440
|
+
export type RagFilter = string[];
|
|
441
|
+
/** Per-column filters surfaced through the column-header dropdowns. */
|
|
442
|
+
export interface ColumnFilters {
|
|
443
|
+
/** Name "contains" — column-scoped text filter, distinct from the
|
|
444
|
+
* global search box. */
|
|
445
|
+
name: string;
|
|
446
|
+
/** File category multi-select (Image / Video / Code / …). */
|
|
447
|
+
type: TypeFilter;
|
|
448
|
+
/** Extension "contains" — e.g. "pdf", "jp" matches jpg & jpeg. */
|
|
449
|
+
extension: string;
|
|
450
|
+
/** MIME "contains" — e.g. "image/" matches every image. */
|
|
451
|
+
mime: string;
|
|
452
|
+
/** Folder path "contains" — useful in tree-wide search results. */
|
|
453
|
+
path: string;
|
|
454
|
+
/** Owner user-id multi-select. */
|
|
455
|
+
owner: OwnerFilter;
|
|
456
|
+
modified: ModifiedFilter;
|
|
457
|
+
/** Same preset semantics as `modified`, applied to `createdAt`. */
|
|
458
|
+
created: ModifiedFilter;
|
|
459
|
+
size: SizeFilter;
|
|
460
|
+
access: AccessFilter;
|
|
461
|
+
/**
|
|
462
|
+
* Knowledge indexing status multi-select. Only meaningful when the user has
|
|
463
|
+
* fetched Knowledge statuses (via the Knowledge column toggle in column-settings or
|
|
464
|
+
* the column-header refresh button) — folders never pass this filter
|
|
465
|
+
* since Knowledge indexing is a file-only concept.
|
|
466
|
+
*/
|
|
467
|
+
rag: RagFilter;
|
|
468
|
+
}
|
|
469
|
+
/**
|
|
470
|
+
* Stable ids for every optional / required column rendered in the file
|
|
471
|
+
* table. Hidden vs. visible columns are tracked in
|
|
472
|
+
* `UiState.visibleColumns` — a Box.com / Google-Drive-style "Choose
|
|
473
|
+
* columns" panel toggles each on/off. `name` and `access` are conceptually
|
|
474
|
+
* always present but are still included here so the type remains the
|
|
475
|
+
* single source of truth.
|
|
476
|
+
*/
|
|
477
|
+
export type ColumnId = "name" | "type" | "extension" | "mime" | "path" | "owner" | "size" | "version" | "updated_at" | "created_at" | "access" | "rag_status" | "context";
|
|
478
|
+
export type VisibleColumns = Record<ColumnId, boolean>;
|
|
479
|
+
/**
|
|
480
|
+
* Default column set on first load. Tuned to match what users coming from
|
|
481
|
+
* Box.com / Google Drive expect to see by default — Type is shown
|
|
482
|
+
* because "what kind of file is this" is the second-most-important
|
|
483
|
+
* question after "what's its name", and the previous shipping default
|
|
484
|
+
* (just Name / Modified / Size / Access) silently hid that signal.
|
|
485
|
+
*
|
|
486
|
+
* `rag_status` is OFF by default because populating it requires a per-file
|
|
487
|
+
* network probe that the user explicitly opts into.
|
|
488
|
+
*/
|
|
489
|
+
export declare const DEFAULT_VISIBLE_COLUMNS: VisibleColumns;
|
|
490
|
+
export interface UiState {
|
|
491
|
+
viewMode: ViewMode;
|
|
492
|
+
sortBy: SortBy;
|
|
493
|
+
sortDir: SortDirection;
|
|
494
|
+
/** Files-only / folders-only / both. Default = "all". */
|
|
495
|
+
kindFilter: KindFilter;
|
|
496
|
+
/** Whether to show extra detail columns (Extension, Type, Owner, …). */
|
|
497
|
+
detailsLevel: DetailsLevel;
|
|
498
|
+
/** Per-column filter values driven by the column-header dropdowns. */
|
|
499
|
+
columnFilters: ColumnFilters;
|
|
500
|
+
/** Which optional columns are mounted in the file table. */
|
|
501
|
+
visibleColumns: VisibleColumns;
|
|
502
|
+
/**
|
|
503
|
+
* Tree-wide search box value. Lives in Redux (not local component state)
|
|
504
|
+
* so the URL-sync layer can reflect it as `?q=…` and so cross-component
|
|
505
|
+
* reads (e.g. clearing search from a chip) don't need callbacks.
|
|
506
|
+
*/
|
|
507
|
+
searchQuery: string;
|
|
508
|
+
/**
|
|
509
|
+
* Sticky filter chip currently active (Recents / Starred). Independent
|
|
510
|
+
* of `kindFilter` — chips apply preset semantics on top of any column
|
|
511
|
+
* filters. `null` = no chip.
|
|
512
|
+
*/
|
|
513
|
+
chipFilter: ChipFilter | null;
|
|
514
|
+
activeFileId: string | null;
|
|
515
|
+
activeFolderId: string | null;
|
|
516
|
+
/**
|
|
517
|
+
* The single item (file or folder id) that has "keyboard/visual focus" — the
|
|
518
|
+
* highlighted row in the Google Drive sense. Set after create/upload so the
|
|
519
|
+
* newly-created item is immediately highlighted and scrolled into view.
|
|
520
|
+
* Clicking any row also moves focus to that row.
|
|
521
|
+
*/
|
|
522
|
+
focusedId: string | null;
|
|
523
|
+
}
|
|
524
|
+
export interface SelectionState {
|
|
525
|
+
selectedIds: string[];
|
|
526
|
+
anchorId: string | null;
|
|
527
|
+
}
|
|
528
|
+
export type UploadStatus = "pending" | "uploading" | "success" | "error" | "cancelled";
|
|
529
|
+
export interface UploadState {
|
|
530
|
+
requestId: string;
|
|
531
|
+
fileName: string;
|
|
532
|
+
fileSize: number;
|
|
533
|
+
parentFolderId: string | null;
|
|
534
|
+
/**
|
|
535
|
+
* The logical `folderPath` this upload targeted (`UploadFilesArg.folderPath`),
|
|
536
|
+
* verbatim — `null` when the caller used `parentFolderId` instead.
|
|
537
|
+
*
|
|
538
|
+
* This is the ONE correlation channel a container-scoped surface has for
|
|
539
|
+
* finding its own failed uploads: `state.uploads` is a flat, app-wide map
|
|
540
|
+
* (every uploader shares it — the Files page, chat attachments, the
|
|
541
|
+
* Rulebook Resources card…), so a surface that wants "MY failed uploads,
|
|
542
|
+
* not anyone else's" gives itself a folder path nothing else uses and reads
|
|
543
|
+
* back entries matching it exactly (see
|
|
544
|
+
* `RulebookSourcesPanel.tsx`'s `sourcesFolderPath`).
|
|
545
|
+
*/
|
|
546
|
+
folderPath: string | null;
|
|
547
|
+
status: UploadStatus;
|
|
548
|
+
bytesUploaded: number;
|
|
549
|
+
startedAt: number;
|
|
550
|
+
completedAt: number | null;
|
|
551
|
+
error: string | null;
|
|
552
|
+
retries: number;
|
|
553
|
+
/** Populated on success; null until then. */
|
|
554
|
+
fileId: string | null;
|
|
555
|
+
}
|
|
556
|
+
/**
|
|
557
|
+
* Per-file Knowledge indexing status, hydrated lazily by the
|
|
558
|
+
* `prefetchRagStatusesForFiles` thunk. The thunk de-duplicates against
|
|
559
|
+
* `byFileId` so toggling the Knowledge column on/off doesn't re-fetch already
|
|
560
|
+
* known answers (use the column header's refresh action to force).
|
|
561
|
+
*/
|
|
562
|
+
export interface RagStatusState {
|
|
563
|
+
byFileId: Record<string, RagStatus>;
|
|
564
|
+
/** True while a batch fetch is in flight. */
|
|
565
|
+
isFetching: boolean;
|
|
566
|
+
/** ms timestamp of the most-recent successful batch (any source). */
|
|
567
|
+
lastFetchedAt: number | null;
|
|
568
|
+
}
|
|
569
|
+
export interface CloudFilesState {
|
|
570
|
+
filesById: Record<string, CloudFileRecord>;
|
|
571
|
+
foldersById: Record<string, CloudFolderRecord>;
|
|
572
|
+
versionsByFileId: Record<string, CloudFileVersion[]>;
|
|
573
|
+
permissionsByResourceId: Record<string, CloudFilePermission[]>;
|
|
574
|
+
shareLinksByResourceId: Record<string, CloudShareLink[]>;
|
|
575
|
+
tree: TreeState;
|
|
576
|
+
selection: SelectionState;
|
|
577
|
+
ui: UiState;
|
|
578
|
+
uploads: Record<string, UploadState>;
|
|
579
|
+
/** Per-file Knowledge indexing status, populated on demand. */
|
|
580
|
+
ragStatus: RagStatusState;
|
|
581
|
+
/**
|
|
582
|
+
* Realtime attachment status. Mirrors the supabase Channel lifecycle.
|
|
583
|
+
*/
|
|
584
|
+
realtime: {
|
|
585
|
+
status: "detached" | "connecting" | "subscribed" | "errored" | "closed";
|
|
586
|
+
userId: string | null;
|
|
587
|
+
lastEventAt: number | null;
|
|
588
|
+
error: string | null;
|
|
589
|
+
};
|
|
590
|
+
}
|
|
591
|
+
export interface CreateFolderArg {
|
|
592
|
+
folderName: string;
|
|
593
|
+
parentId: string | null;
|
|
594
|
+
visibility?: Visibility;
|
|
595
|
+
metadata?: Record<string, unknown>;
|
|
596
|
+
}
|
|
597
|
+
export interface DeleteFolderArg {
|
|
598
|
+
folderId: string;
|
|
599
|
+
}
|
|
600
|
+
export interface EnsureFolderPathArg {
|
|
601
|
+
/**
|
|
602
|
+
* A folder path like "Images/2026/Q1". Each segment is created if missing.
|
|
603
|
+
* Returns the leaf folder's id.
|
|
604
|
+
*/
|
|
605
|
+
folderPath: string;
|
|
606
|
+
visibility?: Visibility;
|
|
607
|
+
}
|
|
608
|
+
export interface UploadFilesArg {
|
|
609
|
+
files: File[];
|
|
610
|
+
/**
|
|
611
|
+
* Existing folder id (rare — only when you've already created/loaded the
|
|
612
|
+
* folder via realtime or the tree RPC). Prefer `folderPath` for new
|
|
613
|
+
* uploads — the backend auto-creates folders so the browser doesn't
|
|
614
|
+
* need to query `cld_folders` (which can recurse on RLS until the
|
|
615
|
+
* SECURITY DEFINER policy fix lands; see HANDOFF.md).
|
|
616
|
+
*/
|
|
617
|
+
parentFolderId?: string | null;
|
|
618
|
+
/**
|
|
619
|
+
* Logical folder path (e.g. "Images/Chat" or "Debug Uploads"). The
|
|
620
|
+
* Python backend creates any missing folders during upload. This is
|
|
621
|
+
* the recommended option for new uploads — it doesn't trigger any
|
|
622
|
+
* supabase-js queries on `cld_folders` from the browser.
|
|
623
|
+
*/
|
|
624
|
+
folderPath?: string | null;
|
|
625
|
+
visibility?: Visibility;
|
|
626
|
+
shareWith?: string[];
|
|
627
|
+
shareLevel?: PermissionLevel;
|
|
628
|
+
changeSummary?: string;
|
|
629
|
+
metadata?: Record<string, unknown>;
|
|
630
|
+
/**
|
|
631
|
+
* Per-upload options forwarded to the backend `options_json` field.
|
|
632
|
+
* Notably `rag.trigger_now` to run Knowledge immediately on upload instead of
|
|
633
|
+
* waiting for the scheduled auto-Knowledge sweep. Only menu/explicit uploads set
|
|
634
|
+
* this — drag-drop leaves it unset (scheduled sweep still runs).
|
|
635
|
+
*/
|
|
636
|
+
options?: {
|
|
637
|
+
rag?: {
|
|
638
|
+
trigger_now?: boolean;
|
|
639
|
+
};
|
|
640
|
+
};
|
|
641
|
+
/** Parallel upload ceiling. Defaults to 3. */
|
|
642
|
+
concurrency?: number;
|
|
643
|
+
/**
|
|
644
|
+
* Per-file path overrides. Keyed by index into `files` (string
|
|
645
|
+
* because Records use string keys). When set for a file, the
|
|
646
|
+
* upload uses this exact path (relative to the parent prefix)
|
|
647
|
+
* instead of the file's own name — i.e. it can target an EXISTING
|
|
648
|
+
* file's path so the backend version-bumps it.
|
|
649
|
+
*
|
|
650
|
+
* The auto " (1)" / " (2)" rename in `uploadFiles` is bypassed
|
|
651
|
+
* for any index that has an override, since the override is the
|
|
652
|
+
* user's explicit choice (typically "Overwrite" from the
|
|
653
|
+
* duplicate-upload dialog).
|
|
654
|
+
*
|
|
655
|
+
* Indices not present here use the default name resolution.
|
|
656
|
+
*/
|
|
657
|
+
filenameOverrides?: Record<number, string>;
|
|
658
|
+
/**
|
|
659
|
+
* Indices to drop from the upload entirely. Used by the
|
|
660
|
+
* duplicate-upload dialog's "Skip" action — we want to keep the
|
|
661
|
+
* batch shape stable for telemetry, but not actually upload
|
|
662
|
+
* those files. Indices reference the original `files` array.
|
|
663
|
+
*/
|
|
664
|
+
skipIndices?: number[];
|
|
665
|
+
/**
|
|
666
|
+
* Indices whose duplicate-dialog decision was explicitly "Make a copy".
|
|
667
|
+
* The upload keeps its unique display name and sends the backend's strict
|
|
668
|
+
* `force_new_copy` intent so checksum-identical bytes create a distinct,
|
|
669
|
+
* durable row linked through `duplicate_of_file_id`.
|
|
670
|
+
*/
|
|
671
|
+
forceNewCopyIndices?: number[];
|
|
672
|
+
}
|
|
673
|
+
export interface RenameFileArg {
|
|
674
|
+
fileId: string;
|
|
675
|
+
newName: string;
|
|
676
|
+
}
|
|
677
|
+
export interface MoveFileArg {
|
|
678
|
+
fileId: string;
|
|
679
|
+
newParentFolderId: string | null;
|
|
680
|
+
}
|
|
681
|
+
export interface UpdateFileMetadataArg {
|
|
682
|
+
fileId: string;
|
|
683
|
+
patch: {
|
|
684
|
+
visibility?: Visibility;
|
|
685
|
+
metadata?: Record<string, unknown>;
|
|
686
|
+
};
|
|
687
|
+
}
|
|
688
|
+
export interface DeleteFileArg {
|
|
689
|
+
fileId: string;
|
|
690
|
+
}
|
|
691
|
+
/**
|
|
692
|
+
* Save new content as the next version of an EXISTING file (edit-in-place).
|
|
693
|
+
* `content` is the whole new body; the file keeps its id, name and folder.
|
|
694
|
+
*/
|
|
695
|
+
export interface SaveFileNewVersionArg {
|
|
696
|
+
fileId: string;
|
|
697
|
+
content: string | Blob;
|
|
698
|
+
changeSummary?: string;
|
|
699
|
+
}
|
|
700
|
+
/** What a successful save wrote: the same file id at its new version. */
|
|
701
|
+
export interface SaveFileNewVersionResult {
|
|
702
|
+
fileId: string;
|
|
703
|
+
versionNumber: number;
|
|
704
|
+
}
|
|
705
|
+
export interface RestoreVersionArg {
|
|
706
|
+
fileId: string;
|
|
707
|
+
versionNumber: number;
|
|
708
|
+
}
|
|
709
|
+
/**
|
|
710
|
+
* Grant/revoke via the by-grantee REST path. `granteeType` here is a `user` or
|
|
711
|
+
* `group` grant only — `"public"` is not a by-grantee grant (it is toggled on
|
|
712
|
+
* the resource's visibility), and the grant/revoke thunks throw if it is passed
|
|
713
|
+
* (see `requireByGranteeType` in `redux/thunks.ts`). Defaults to `"user"`.
|
|
714
|
+
*/
|
|
715
|
+
export interface GrantPermissionArg {
|
|
716
|
+
resourceId: string;
|
|
717
|
+
resourceType: ResourceType;
|
|
718
|
+
granteeId: string;
|
|
719
|
+
granteeType?: GranteeType;
|
|
720
|
+
level: PermissionLevel;
|
|
721
|
+
expiresAt?: string;
|
|
722
|
+
}
|
|
723
|
+
export interface RevokePermissionArg {
|
|
724
|
+
resourceId: string;
|
|
725
|
+
resourceType: ResourceType;
|
|
726
|
+
granteeId: string;
|
|
727
|
+
granteeType?: GranteeType;
|
|
728
|
+
}
|
|
729
|
+
export interface CreateShareLinkArg {
|
|
730
|
+
resourceId: string;
|
|
731
|
+
resourceType: ResourceType;
|
|
732
|
+
permissionLevel: "viewer" | "editor";
|
|
733
|
+
expiresAt?: string;
|
|
734
|
+
maxUses?: number;
|
|
735
|
+
}
|
|
736
|
+
export interface RevokeShareLinkArg {
|
|
737
|
+
/** `platform.share_links.id` — the canonical revoke key. */
|
|
738
|
+
linkId: string;
|
|
739
|
+
}
|
|
740
|
+
/**
|
|
741
|
+
* Request body for `POST /folders` — create a folder. The Python team
|
|
742
|
+
* accepts a logical path (e.g. "Images/Chat") OR an explicit name + parentId.
|
|
743
|
+
* Path-style is preferred because the backend creates intermediate folders
|
|
744
|
+
* atomically and idempotently, matching upload's auto-create semantics.
|
|
745
|
+
*/
|
|
746
|
+
/**
|
|
747
|
+
* Body for `POST /folders`. Path-style ONLY — the backend creates any missing
|
|
748
|
+
* segments and REJECTS `{folder_name, parent_id}` (`validation_error`).
|
|
749
|
+
* DERIVED from the contract so the phantom name/parent fields can't be sent.
|
|
750
|
+
*/
|
|
751
|
+
export type CreateFolderRequest = components["schemas"]["CreateFolderRequest"];
|
|
752
|
+
/**
|
|
753
|
+
* Body for `PATCH /folders/{id}`. Rename AND move are expressed as the target
|
|
754
|
+
* `folder_path` — the server renames, reparents, and cascades descendants from
|
|
755
|
+
* it. The backend SILENTLY IGNORES `folder_name`/`parent_id` (they are not on
|
|
756
|
+
* the model), so sending them made rename/move no-op server-side. DERIVED from
|
|
757
|
+
* the contract to make that mistake a compile error.
|
|
758
|
+
*/
|
|
759
|
+
export type FolderPatchRequest = components["schemas"]["PatchFolderRequest"];
|
|
760
|
+
/** Body for `DELETE /files/bulk`. */
|
|
761
|
+
export interface BulkDeleteFilesRequest {
|
|
762
|
+
file_ids: string[];
|
|
763
|
+
}
|
|
764
|
+
/** Body for `POST /files/bulk/move`. */
|
|
765
|
+
export interface BulkMoveFilesRequest {
|
|
766
|
+
file_ids: string[];
|
|
767
|
+
/** Target parent folder id, or null to move to root. */
|
|
768
|
+
new_parent_folder_id: string | null;
|
|
769
|
+
}
|
|
770
|
+
/** Body for `POST /folders/bulk/move`. */
|
|
771
|
+
export interface BulkMoveFoldersRequest {
|
|
772
|
+
folder_ids: string[];
|
|
773
|
+
/** Target parent folder id, or null to move to root. */
|
|
774
|
+
new_parent_id: string | null;
|
|
775
|
+
}
|
|
776
|
+
/**
|
|
777
|
+
* Per-item outcome inside a bulk response. Matches the backend
|
|
778
|
+
* `BulkResultItem` shape: `{ id, ok, error }` per item plus the
|
|
779
|
+
* aggregate counters returned alongside.
|
|
780
|
+
*/
|
|
781
|
+
export interface BulkResultItem {
|
|
782
|
+
id: string;
|
|
783
|
+
ok: boolean;
|
|
784
|
+
/** Error code/message string when `ok` is false; null on success. */
|
|
785
|
+
error: string | null;
|
|
786
|
+
}
|
|
787
|
+
/**
|
|
788
|
+
* Aggregate envelope returned by every bulk endpoint:
|
|
789
|
+
* - `DELETE /files/bulk`
|
|
790
|
+
* - `POST /files/bulk/move`
|
|
791
|
+
* - `POST /folders/bulk/move`
|
|
792
|
+
*
|
|
793
|
+
* The aggregate `succeeded` and `failed` are NUMBERS (counts), not
|
|
794
|
+
* arrays. Per-item detail lives in `results`.
|
|
795
|
+
*/
|
|
796
|
+
export interface BulkResponse {
|
|
797
|
+
results: BulkResultItem[];
|
|
798
|
+
succeeded: number;
|
|
799
|
+
failed: number;
|
|
800
|
+
}
|
|
801
|
+
/**
|
|
802
|
+
* Response body of `GET /files/usage`. Drives the storage indicator,
|
|
803
|
+
* the tier badge, and feature gating in the UI. `null` on a numeric
|
|
804
|
+
* field means "no cap" (typically Enterprise tier).
|
|
805
|
+
*/
|
|
806
|
+
export interface StorageUsageResponse {
|
|
807
|
+
tier_id: string;
|
|
808
|
+
tier_name: string;
|
|
809
|
+
is_blocked: boolean;
|
|
810
|
+
blocked_reason: string | null;
|
|
811
|
+
bytes_used: number;
|
|
812
|
+
files_count: number;
|
|
813
|
+
daily_upload_count: number;
|
|
814
|
+
daily_upload_bytes: number;
|
|
815
|
+
max_storage_bytes: number | null;
|
|
816
|
+
max_file_size_bytes: number | null;
|
|
817
|
+
max_files: number | null;
|
|
818
|
+
max_versions_per_file: number | null;
|
|
819
|
+
max_daily_uploads: number | null;
|
|
820
|
+
max_daily_upload_bytes: number | null;
|
|
821
|
+
max_bulk_items: number | null;
|
|
822
|
+
rate_limit_uploads_per_min: number | null;
|
|
823
|
+
rate_limit_downloads_per_min: number | null;
|
|
824
|
+
features: Record<string, unknown>;
|
|
825
|
+
/**
|
|
826
|
+
* TRUE when `files.user_storage_usage` actually holds a row for this user.
|
|
827
|
+
*
|
|
828
|
+
* `get_usage_status` synthesizes `{bytes_used: 0, files_count: 0, …}` when
|
|
829
|
+
* the ledger row is ABSENT, which on screen is indistinguishable from a
|
|
830
|
+
* genuinely empty account — so the UI would say "0 bytes of 5 GB" about an
|
|
831
|
+
* account whose usage nobody has measured. The flattener detects the
|
|
832
|
+
* synthesized shape (it carries no `user_id`) and says so here, and the
|
|
833
|
+
* meter renders "usage being recalculated" instead of a number it does not
|
|
834
|
+
* have. folder-sync SPEC-SERVER §8: metering only just landed on the
|
|
835
|
+
* standalone service, and FS-L6 still has to rebuild and re-grain the
|
|
836
|
+
* ledger — an unbuilt row is the expected state, not an error.
|
|
837
|
+
*/
|
|
838
|
+
ledger_measured: boolean;
|
|
839
|
+
/** When the ledger row was last written, or null when there is no row. */
|
|
840
|
+
ledger_measured_at: string | null;
|
|
841
|
+
}
|
|
842
|
+
/**
|
|
843
|
+
* Response body of `GET /files/trash`. Soft-deleted files + folders for
|
|
844
|
+
* the authenticated user (or guest fingerprint).
|
|
845
|
+
*/
|
|
846
|
+
export interface TrashListResponse {
|
|
847
|
+
files: FileRecordApi[];
|
|
848
|
+
folders: CloudFolderRow[];
|
|
849
|
+
}
|
|
850
|
+
/**
|
|
851
|
+
* Response body of `GET /files/search?q=&mime_prefix=&limit=&offset=`.
|
|
852
|
+
* The backend matches against filename + path substring; `mime_prefix`
|
|
853
|
+
* filters by `mime_type LIKE 'prefix%'`.
|
|
854
|
+
*/
|
|
855
|
+
export interface SearchFilesResponse {
|
|
856
|
+
results: FileRecordApi[];
|
|
857
|
+
query: string;
|
|
858
|
+
total_returned: number;
|
|
859
|
+
}
|
|
860
|
+
export interface SearchFilesParams {
|
|
861
|
+
q: string;
|
|
862
|
+
mimePrefix?: string;
|
|
863
|
+
limit?: number;
|
|
864
|
+
offset?: number;
|
|
865
|
+
}
|
|
866
|
+
export interface RenameFileRequest {
|
|
867
|
+
/** Full new logical path including filename. Backend auto-creates parents. */
|
|
868
|
+
new_path: string;
|
|
869
|
+
}
|
|
870
|
+
export interface CopyFileRequest {
|
|
871
|
+
/** Full target logical path including filename. Backend auto-creates parents. */
|
|
872
|
+
target_path: string;
|
|
873
|
+
/** Default false. When false, conflicts return `409 file_already_exists`. */
|
|
874
|
+
overwrite?: boolean;
|
|
875
|
+
}
|
|
876
|
+
export interface BulkDeleteFilesArg {
|
|
877
|
+
fileIds: string[];
|
|
878
|
+
}
|
|
879
|
+
export interface BulkMoveFilesArg {
|
|
880
|
+
fileIds: string[];
|
|
881
|
+
newParentFolderId: string | null;
|
|
882
|
+
}
|
|
883
|
+
export interface BulkMoveFoldersArg {
|
|
884
|
+
folderIds: string[];
|
|
885
|
+
newParentId: string | null;
|
|
886
|
+
}
|
|
887
|
+
export interface UpdateFolderArg {
|
|
888
|
+
folderId: string;
|
|
889
|
+
patch: {
|
|
890
|
+
folderName?: string;
|
|
891
|
+
parentId?: string | null;
|
|
892
|
+
visibility?: Visibility;
|
|
893
|
+
metadata?: Record<string, unknown>;
|
|
894
|
+
};
|
|
895
|
+
}
|
|
896
|
+
export interface RenameFileToPathArg {
|
|
897
|
+
fileId: string;
|
|
898
|
+
/** Full new logical path including filename. */
|
|
899
|
+
newPath: string;
|
|
900
|
+
}
|
|
901
|
+
export interface CopyFileArg {
|
|
902
|
+
fileId: string;
|
|
903
|
+
/** Full target logical path including filename. */
|
|
904
|
+
targetPath: string;
|
|
905
|
+
overwrite?: boolean;
|
|
906
|
+
}
|
|
907
|
+
export interface SearchFilesArg {
|
|
908
|
+
q: string;
|
|
909
|
+
mimePrefix?: string;
|
|
910
|
+
limit?: number;
|
|
911
|
+
offset?: number;
|
|
912
|
+
}
|
|
913
|
+
export type RequestKind = "upload" | "update" | "delete" | "move" | "rename" | "restore-version" | "grant-permission" | "revoke-permission" | "create-share-link" | "revoke-share-link" | "folder-create" | "folder-update" | "folder-delete" | "bulk-delete-files" | "file-rename-path" | "file-copy" | "file-restore" | "bulk-move-files" | "bulk-move-folders";
|
|
914
|
+
export interface LedgerEntry {
|
|
915
|
+
requestId: string;
|
|
916
|
+
kind: RequestKind;
|
|
917
|
+
resourceId: string | null;
|
|
918
|
+
resourceType: ResourceType | null;
|
|
919
|
+
createdAt: number;
|
|
920
|
+
}
|
|
921
|
+
export type { BackendApiError } from "@ai-matrx/agents/matrx";
|
|
922
|
+
/**
|
|
923
|
+
* Files-specific error codes.
|
|
924
|
+
*
|
|
925
|
+
* Aligned with the Python backend's error envelope. The retry posture
|
|
926
|
+
* column is the FE contract — every caller must respect it:
|
|
927
|
+
*
|
|
928
|
+
* | Code | HTTP | Retry? | UX hint |
|
|
929
|
+
* |---------------------------|------|---------|--------------------------|
|
|
930
|
+
* | invalid_request | 400 | no | fix the request |
|
|
931
|
+
* | invalid_path | 400 | no | fix the path |
|
|
932
|
+
* | invalid_metadata | 400 | no | fix the patch |
|
|
933
|
+
* | fingerprint_required | 400 | no | guest header missing |
|
|
934
|
+
* | auth_required | 401 | no | sign in |
|
|
935
|
+
* | permission_denied | 403 | no | request access |
|
|
936
|
+
* | guest_id_mismatch | 403 | no | re-auth |
|
|
937
|
+
* | not_found | 404 | no | resource gone |
|
|
938
|
+
* | conflict | 409 | no | overwrite=true to force |
|
|
939
|
+
* | file_already_exists | 409 | no | overwrite=true to force |
|
|
940
|
+
* | guest_locked | 409 | no | already migrated |
|
|
941
|
+
* | file_too_large | 413 | no | upgrade tier |
|
|
942
|
+
* | storage_quota_exceeded | 413 | no | upgrade tier |
|
|
943
|
+
* | file_count_exceeded | 413 | no | upgrade tier |
|
|
944
|
+
* | daily_uploads_exceeded | 413 | no | wait until tomorrow |
|
|
945
|
+
* | daily_bytes_exceeded | 413 | no | wait until tomorrow |
|
|
946
|
+
* | bulk_too_large | 413 | no | smaller batch |
|
|
947
|
+
* | rate_limited | 429 | YES (b) | exponential backoff |
|
|
948
|
+
* | account_blocked | 423 | no | contact support |
|
|
949
|
+
* | share_link_invalid | 410 | no | link revoked / expired |
|
|
950
|
+
* | internal | 5xx | YES (b) | exponential backoff |
|
|
951
|
+
* | cld_sync_unavailable | 503 | YES (b) | exponential backoff |
|
|
952
|
+
*/
|
|
953
|
+
export type CloudFilesErrorCode = "invalid_request" | "invalid_path" | "invalid_metadata" | "fingerprint_required" | "auth_required" | "permission_denied" | "guest_id_mismatch" | "not_found" | "conflict" | "file_already_exists" | "guest_locked" | "file_too_large" | "storage_quota_exceeded" | "file_count_exceeded" | "daily_uploads_exceeded" | "daily_bytes_exceeded" | "bulk_too_large" | "rate_limited" | "account_blocked" | "share_link_invalid" | "internal" | "cld_sync_unavailable";
|
|
954
|
+
export declare function isCloudTreeFileRow(row: CloudTreeRow): row is CloudTreeFileRow;
|
|
955
|
+
export declare function isCloudTreeFolderRow(row: CloudTreeRow): row is CloudTreeFolderRow;
|
|
956
|
+
/**
|
|
957
|
+
* Preset name accepted by `POST /assets` and `POST /assets/{id}/variants`.
|
|
958
|
+
* Adding a new preset on the backend is a coordinated change — extend
|
|
959
|
+
* this union in lockstep.
|
|
960
|
+
*/
|
|
961
|
+
export type AssetPreset = "raw" | "podcast" | "social" | "web" | "email" | "logo" | "avatar" | "favicon";
|
|
962
|
+
/**
|
|
963
|
+
* One rendered variant inside an `Asset`. Each variant is a distinct
|
|
964
|
+
* `cld_files` row (so it shows up in the file tree and has its own
|
|
965
|
+
* permissions) but the `Asset` envelope groups them under a single
|
|
966
|
+
* `primary_key`-rooted record.
|
|
967
|
+
*
|
|
968
|
+
* URL fields (all durable — none expire):
|
|
969
|
+
* - `url` canonical inline-renderable URL. CDN for public,
|
|
970
|
+
* the durable download route for private/shared.
|
|
971
|
+
* **Use this** for `<img src>` / `<video src>` /
|
|
972
|
+
* `<audio src>`.
|
|
973
|
+
* - `cdn_url` permanent CDN URL when public + CDN configured;
|
|
974
|
+
* null otherwise.
|
|
975
|
+
* - `download_url` durable URL with `Content-Disposition: attachment`
|
|
976
|
+
* — forces a download dialog.
|
|
977
|
+
*/
|
|
978
|
+
export interface AssetVariant {
|
|
979
|
+
key: string;
|
|
980
|
+
file_id: string;
|
|
981
|
+
file_path: string;
|
|
982
|
+
width: number | null;
|
|
983
|
+
height: number | null;
|
|
984
|
+
mime_type: string | null;
|
|
985
|
+
/**
|
|
986
|
+
* File size in bytes. Renamed from `file_size` in Phase 0 (see
|
|
987
|
+
* docs/PYTHON_UPDATES.md §3). Old servers may still send `file_size`
|
|
988
|
+
* during the transition — consumers should fall back to the legacy
|
|
989
|
+
* field name when reading older responses.
|
|
990
|
+
*/
|
|
991
|
+
size_bytes: number | null;
|
|
992
|
+
/**
|
|
993
|
+
* Legacy alias for `size_bytes`. Populated by older servers during
|
|
994
|
+
* the Phase 0 transition window. Read `size_bytes` first; fall back
|
|
995
|
+
* to this only when re-hydrating older persisted responses.
|
|
996
|
+
*
|
|
997
|
+
* @deprecated Use `size_bytes`.
|
|
998
|
+
*/
|
|
999
|
+
file_size?: number | null;
|
|
1000
|
+
url: string | null;
|
|
1001
|
+
cdn_url: string | null;
|
|
1002
|
+
download_url: string | null;
|
|
1003
|
+
metadata: Record<string, unknown>;
|
|
1004
|
+
}
|
|
1005
|
+
/**
|
|
1006
|
+
* Canonical envelope returned by every `/assets/*` and
|
|
1007
|
+
* `/files/{id}/asset` endpoint.
|
|
1008
|
+
*
|
|
1009
|
+
* - `file_id` master file id (the upload's "original" row).
|
|
1010
|
+
* - `primary_key` which variant the FE should render by default
|
|
1011
|
+
* (e.g. `"cover_url"` for podcast, `"original"` for raw).
|
|
1012
|
+
* - `primary_url` shorthand for `variants[primary_key].url`.
|
|
1013
|
+
* - `variants` always includes `"original"` plus zero or more
|
|
1014
|
+
* preset-driven derivatives.
|
|
1015
|
+
*/
|
|
1016
|
+
export interface Asset {
|
|
1017
|
+
file_id: string;
|
|
1018
|
+
visibility: Visibility;
|
|
1019
|
+
folder: string;
|
|
1020
|
+
preset: string | null;
|
|
1021
|
+
primary_key: string;
|
|
1022
|
+
primary_url: string | null;
|
|
1023
|
+
variants: Record<string, AssetVariant>;
|
|
1024
|
+
metadata: Record<string, unknown>;
|
|
1025
|
+
}
|
|
1026
|
+
/**
|
|
1027
|
+
* Request body for `POST /assets/{id}/variants`. Sends additional
|
|
1028
|
+
* variants for an already-uploaded file. Idempotent on `(file_id, key)`.
|
|
1029
|
+
*/
|
|
1030
|
+
export interface AddAssetVariantsRequest {
|
|
1031
|
+
preset?: AssetPreset;
|
|
1032
|
+
custom_variants?: ReadonlyArray<{
|
|
1033
|
+
key: string;
|
|
1034
|
+
suffix?: string;
|
|
1035
|
+
width?: number;
|
|
1036
|
+
height?: number;
|
|
1037
|
+
quality?: number;
|
|
1038
|
+
format?: string;
|
|
1039
|
+
}>;
|
|
1040
|
+
include_social_baseline?: boolean;
|
|
1041
|
+
}
|
|
1042
|
+
/**
|
|
1043
|
+
* Request body for `PATCH /assets/{id}`. Sparse — any field omitted is
|
|
1044
|
+
* left unchanged. `metadata` is MERGED server-side (matches the
|
|
1045
|
+
* `/files/{id}` PATCH semantics).
|
|
1046
|
+
*/
|
|
1047
|
+
export interface AssetPatchRequest {
|
|
1048
|
+
visibility?: Visibility;
|
|
1049
|
+
/** Comma-separated user IDs OR an explicit list. */
|
|
1050
|
+
share_with?: string | ReadonlyArray<string>;
|
|
1051
|
+
share_level?: PermissionLevel;
|
|
1052
|
+
metadata?: Record<string, unknown>;
|
|
1053
|
+
}
|
|
1054
|
+
/**
|
|
1055
|
+
* One row in the `GET /assets/presets` response — describes a single
|
|
1056
|
+
* variant a preset will render.
|
|
1057
|
+
*/
|
|
1058
|
+
export interface AssetPresetVariantDescriptor {
|
|
1059
|
+
key: string;
|
|
1060
|
+
width: number | null;
|
|
1061
|
+
height: number | null;
|
|
1062
|
+
format: string | null;
|
|
1063
|
+
}
|
|
1064
|
+
/**
|
|
1065
|
+
* One preset row in the `GET /assets/presets` response.
|
|
1066
|
+
*/
|
|
1067
|
+
export interface AssetPresetDescriptor {
|
|
1068
|
+
name: AssetPreset;
|
|
1069
|
+
primary_key: string;
|
|
1070
|
+
include_baseline: boolean;
|
|
1071
|
+
variants: ReadonlyArray<AssetPresetVariantDescriptor>;
|
|
1072
|
+
}
|
|
1073
|
+
/**
|
|
1074
|
+
* Response body for `GET /assets/presets`. Drives the picker UI in
|
|
1075
|
+
* admin tools and lets agents enumerate every server-known preset
|
|
1076
|
+
* without hard-coding the list.
|
|
1077
|
+
*/
|
|
1078
|
+
export interface PresetsRegistryResponse {
|
|
1079
|
+
presets: ReadonlyArray<AssetPresetDescriptor>;
|
|
1080
|
+
social_baseline: ReadonlyArray<AssetPresetVariantDescriptor>;
|
|
1081
|
+
}
|