@ai-matrx/media 0.7.16 → 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.
Files changed (191) hide show
  1. package/CHANGELOG.md +25 -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 +30 -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/dist/files.cjs +1298 -0
  186. package/dist/files.cjs.map +1 -0
  187. package/dist/files.d.cts +1171 -0
  188. package/dist/files.d.ts +1171 -0
  189. package/dist/files.js +1275 -0
  190. package/dist/files.js.map +1 -0
  191. package/package.json +404 -9
@@ -0,0 +1,365 @@
1
+ import { classify } from "../utils/classify.js";
2
+ import { pythonFileInlineUrl, pythonShareUrl } from "../utils/python-base.js";
3
+ import { createTrackedObjectUrl } from "../../../../files.js";
4
+ import {
5
+ EPHEMERAL_CAPS,
6
+ EXTERNAL_CAPS,
7
+ PUBLIC_CAPS
8
+ } from "../intelligence/access.js";
9
+ const STREAM_RENDER_TYPES = /* @__PURE__ */ new Set(["image", "audio", "video", "document"]);
10
+ function normalize(source) {
11
+ switch (source.kind) {
12
+ case "blob":
13
+ return fromBlob(source);
14
+ case "file":
15
+ return fromFile(source);
16
+ case "buffer":
17
+ return fromBuffer(source);
18
+ case "stream":
19
+ return fromStream(source);
20
+ case "data_uri":
21
+ return fromDataUri(source);
22
+ case "base64":
23
+ return fromBase64(source);
24
+ case "external_url":
25
+ return fromExternalUrl(source);
26
+ case "youtube":
27
+ return fromYouTube(source);
28
+ case "cloud_file":
29
+ return fromCloudFile(source.cloudFile, source);
30
+ case "file_id":
31
+ return fromFileId(source);
32
+ case "share_link":
33
+ return fromShareLink(source);
34
+ case "public_cdn":
35
+ return fromPublicCdn(source);
36
+ case "upload_result":
37
+ return fromUploadResult(source);
38
+ case "stream_event":
39
+ return fromStreamEvent(source);
40
+ }
41
+ }
42
+ function fromBlob(source) {
43
+ const url = createTrackedObjectUrl(source.blob);
44
+ const meta = classify({
45
+ fileName: source.fileName,
46
+ mime: source.mime ?? source.blob.type,
47
+ sizeBytes: source.blob.size
48
+ });
49
+ return {
50
+ url,
51
+ origin: "ephemeral",
52
+ capabilities: EPHEMERAL_CAPS,
53
+ meta,
54
+ lifecycle: { refreshable: false, persisted: false },
55
+ scope: {},
56
+ __source: source
57
+ };
58
+ }
59
+ function fromFile(source) {
60
+ const url = createTrackedObjectUrl(source.file);
61
+ const meta = classify({
62
+ fileName: source.file.name,
63
+ mime: source.file.type,
64
+ sizeBytes: source.file.size
65
+ });
66
+ return {
67
+ url,
68
+ origin: "ephemeral",
69
+ capabilities: EPHEMERAL_CAPS,
70
+ meta,
71
+ lifecycle: { refreshable: false, persisted: false },
72
+ scope: {},
73
+ __source: source
74
+ };
75
+ }
76
+ function fromBuffer(source) {
77
+ const blob = bufferToBlob(source.buffer, source.mime);
78
+ const url = createTrackedObjectUrl(blob);
79
+ const meta = classify({
80
+ fileName: source.fileName,
81
+ mime: source.mime,
82
+ sizeBytes: blob.size
83
+ });
84
+ return {
85
+ url,
86
+ origin: "ephemeral",
87
+ capabilities: EPHEMERAL_CAPS,
88
+ meta,
89
+ lifecycle: { refreshable: false, persisted: false },
90
+ scope: {},
91
+ __source: source
92
+ };
93
+ }
94
+ function fromStream(source) {
95
+ return {
96
+ origin: "ephemeral",
97
+ capabilities: EPHEMERAL_CAPS,
98
+ meta: classify({ fileName: source.fileName, mime: source.mime }),
99
+ lifecycle: { refreshable: false, persisted: false },
100
+ scope: {},
101
+ __source: source
102
+ };
103
+ }
104
+ function fromDataUri(source) {
105
+ const match = /^data:([^;,]+)(;base64)?,(.+)$/i.exec(source.dataUri);
106
+ const mime = match?.[1];
107
+ const isBase64 = !!match?.[2];
108
+ const payload = match?.[3] ?? "";
109
+ return {
110
+ url: source.dataUri,
111
+ base64: isBase64 ? payload : void 0,
112
+ origin: "ephemeral",
113
+ capabilities: EPHEMERAL_CAPS,
114
+ meta: classify({ mime }),
115
+ lifecycle: { refreshable: false, persisted: false },
116
+ scope: {},
117
+ __source: source
118
+ };
119
+ }
120
+ function fromBase64(source) {
121
+ return {
122
+ url: `data:${source.mime};base64,${source.base64}`,
123
+ base64: source.base64,
124
+ origin: "ephemeral",
125
+ capabilities: EPHEMERAL_CAPS,
126
+ meta: classify({ fileName: source.fileName, mime: source.mime }),
127
+ lifecycle: { refreshable: false, persisted: false },
128
+ scope: {},
129
+ __source: source
130
+ };
131
+ }
132
+ function fromExternalUrl(source) {
133
+ return {
134
+ url: source.url,
135
+ origin: "external",
136
+ capabilities: EXTERNAL_CAPS,
137
+ meta: classify({
138
+ mime: source.mime,
139
+ fileName: filenameFromUrl(source.url)
140
+ }),
141
+ lifecycle: { refreshable: false, persisted: true },
142
+ scope: {},
143
+ __source: source
144
+ };
145
+ }
146
+ function fromYouTube(source) {
147
+ return {
148
+ url: source.url,
149
+ youtubeUrl: source.url,
150
+ origin: "external",
151
+ capabilities: EXTERNAL_CAPS,
152
+ meta: classify({ mime: "video/youtube", fileName: "youtube.video" }),
153
+ lifecycle: { refreshable: false, persisted: true },
154
+ scope: {},
155
+ __source: source
156
+ };
157
+ }
158
+ function fromCloudFile(cloudFile, source) {
159
+ const meta = classify({
160
+ fileName: cloudFile.fileName,
161
+ mime: cloudFile.mimeType ?? void 0,
162
+ sizeBytes: cloudFile.fileSize ?? void 0,
163
+ checksum: cloudFile.checksum ?? void 0
164
+ });
165
+ const isPublic = cloudFile.visibility === "public";
166
+ const bestUrl = isPublic ? cloudFile.cdnUrl ?? cloudFile.url ?? cloudFile.publicUrl ?? pythonFileInlineUrl(cloudFile.id) : cloudFile.url ?? cloudFile.publicUrl ?? pythonFileInlineUrl(cloudFile.id);
167
+ return {
168
+ fileId: cloudFile.id,
169
+ filePath: cloudFile.filePath,
170
+ url: bestUrl,
171
+ origin: cloudFile.visibility === "public" ? "public" : "owned",
172
+ capabilities: {
173
+ canRead: true,
174
+ canEdit: true,
175
+ canShare: true,
176
+ canDelete: true,
177
+ requiresAuth: cloudFile.visibility !== "public",
178
+ // Durable route URLs (and CDN/public URLs) are fetch-safe: the
179
+ // python-client attaches Authorization headers to our own routes.
180
+ transportSafeForFetch: true
181
+ },
182
+ meta,
183
+ lifecycle: {
184
+ refreshable: true,
185
+ persisted: !cloudFile.deletedAt
186
+ },
187
+ scope: {
188
+ ownerId: cloudFile.ownerId,
189
+ organizationId: cloudFile.organizationId ?? readOrgScope(cloudFile),
190
+ projectId: readProjectScope(cloudFile),
191
+ taskId: readTaskScope(cloudFile)
192
+ },
193
+ derivedFrom: cloudFile.parentFileId ? { fileId: cloudFile.parentFileId, kind: cloudFile.derivationKind ?? "" } : void 0,
194
+ __source: source
195
+ };
196
+ }
197
+ function fromFileId(source) {
198
+ return {
199
+ fileId: source.fileId,
200
+ // The durable inline URL is a pure function of the id — seed it here so
201
+ // render paths are synchronous. The resolver still hydrates metadata and
202
+ // may replace it with a permanent CDN URL for public files.
203
+ url: pythonFileInlineUrl(source.fileId),
204
+ origin: "owned",
205
+ capabilities: {
206
+ canRead: true,
207
+ canEdit: false,
208
+ canShare: false,
209
+ canDelete: false,
210
+ requiresAuth: true,
211
+ transportSafeForFetch: true
212
+ },
213
+ meta: classify({ mime: source.mime }),
214
+ lifecycle: { refreshable: true, persisted: true },
215
+ scope: {},
216
+ __source: source
217
+ };
218
+ }
219
+ function fromShareLink(source) {
220
+ return {
221
+ shareToken: source.token,
222
+ url: pythonShareUrl(source.token),
223
+ origin: "public",
224
+ capabilities: PUBLIC_CAPS,
225
+ meta: classify({ mime: source.mime }),
226
+ lifecycle: { refreshable: true, persisted: true },
227
+ scope: {},
228
+ __source: source
229
+ };
230
+ }
231
+ function fromPublicCdn(source) {
232
+ return {
233
+ fileId: source.fileId,
234
+ url: source.url,
235
+ origin: "public",
236
+ capabilities: { ...PUBLIC_CAPS, transportSafeForFetch: true },
237
+ meta: classify({
238
+ mime: source.mime,
239
+ fileName: filenameFromUrl(source.url)
240
+ }),
241
+ lifecycle: { refreshable: !!source.fileId, persisted: true },
242
+ scope: {},
243
+ __source: source
244
+ };
245
+ }
246
+ function fromUploadResult(source) {
247
+ const r = source.uploadResult;
248
+ return {
249
+ fileId: r.fileId,
250
+ url: r.url,
251
+ origin: "owned",
252
+ capabilities: {
253
+ canRead: true,
254
+ canEdit: !!r.fileId,
255
+ canShare: !!r.fileId,
256
+ canDelete: !!r.fileId,
257
+ requiresAuth: true,
258
+ transportSafeForFetch: true
259
+ },
260
+ meta: classify({
261
+ mime: typeof r.metadata?.mimetype === "string" ? r.metadata.mimetype : void 0,
262
+ fileName: typeof r.metadata?.name === "string" ? r.metadata.name : void 0
263
+ }),
264
+ lifecycle: { refreshable: !!r.fileId, persisted: true },
265
+ scope: {},
266
+ __source: source
267
+ };
268
+ }
269
+ function fromStreamEvent(source) {
270
+ const payload = source.payload;
271
+ if (!payload || typeof payload !== "object") {
272
+ return emptyEphemeral(source);
273
+ }
274
+ const type = String(payload.type ?? "");
275
+ let url;
276
+ let mime;
277
+ if (type === "image_output" || type === "audio_output" || type === "video_output") {
278
+ url = payload.url;
279
+ mime = payload.mime_type;
280
+ } else if (STREAM_RENDER_TYPES.has(type)) {
281
+ const data = payload.data;
282
+ url = data?.src ?? data?.url;
283
+ const meta = payload.metadata;
284
+ mime = meta?.mime_type ?? meta?.mime;
285
+ }
286
+ const fileId = payload.file_id ?? payload.metadata?.file_id;
287
+ return {
288
+ fileId,
289
+ url,
290
+ origin: fileId ? "owned" : "external",
291
+ capabilities: {
292
+ canRead: true,
293
+ canEdit: false,
294
+ canShare: false,
295
+ canDelete: false,
296
+ requiresAuth: !!fileId,
297
+ transportSafeForFetch: !fileId
298
+ },
299
+ meta: classify({ mime, fileName: filenameFromUrl(url ?? "") }),
300
+ lifecycle: { refreshable: !!fileId, persisted: true },
301
+ scope: {},
302
+ __source: source
303
+ };
304
+ }
305
+ function filenameFromUrl(url) {
306
+ if (!url) return void 0;
307
+ try {
308
+ const path = new URL(url, "https://x").pathname;
309
+ const last = path.split("/").pop();
310
+ return last && last.length > 0 ? last : void 0;
311
+ } catch {
312
+ return void 0;
313
+ }
314
+ }
315
+ function emptyEphemeral(source) {
316
+ return {
317
+ origin: "ephemeral",
318
+ capabilities: EPHEMERAL_CAPS,
319
+ meta: classify({}),
320
+ lifecycle: { refreshable: false, persisted: false },
321
+ scope: {},
322
+ __source: source
323
+ };
324
+ }
325
+ function bufferToBlob(buffer, mime) {
326
+ if (buffer instanceof Uint8Array) {
327
+ const copy2 = new Uint8Array(buffer.byteLength);
328
+ copy2.set(buffer);
329
+ return new Blob([copy2.buffer], { type: mime });
330
+ }
331
+ if (buffer instanceof ArrayBuffer) return new Blob([buffer], { type: mime });
332
+ const view = new Uint8Array(buffer);
333
+ const copy = new Uint8Array(view.byteLength);
334
+ copy.set(view);
335
+ return new Blob([copy.buffer], { type: mime });
336
+ }
337
+ function readOrgScope(file) {
338
+ const v = file.metadata?.["scope"];
339
+ if (v && typeof v === "object" && !Array.isArray(v)) {
340
+ const orgId = v.organization_id;
341
+ if (typeof orgId === "string") return orgId;
342
+ }
343
+ return void 0;
344
+ }
345
+ function readProjectScope(file) {
346
+ const v = file.metadata?.["scope"];
347
+ if (v && typeof v === "object" && !Array.isArray(v)) {
348
+ const projectId = v.project_id;
349
+ if (typeof projectId === "string") return projectId;
350
+ }
351
+ return void 0;
352
+ }
353
+ function readTaskScope(file) {
354
+ const v = file.metadata?.["scope"];
355
+ if (v && typeof v === "object" && !Array.isArray(v)) {
356
+ const taskId = v.task_id;
357
+ if (typeof taskId === "string") return taskId;
358
+ }
359
+ return void 0;
360
+ }
361
+ export {
362
+ fromCloudFile,
363
+ normalize
364
+ };
365
+ //# sourceMappingURL=normalize.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../../../../src/files/engine/handler/input/normalize.ts"],"sourcesContent":["/**\n * features/files/handler/input/normalize.ts\n *\n * Pure first-pass: turn a `FileSource` into a partial `NormalizedFile`.\n * \"Pure\" means no I/O, no Redux. Anything that needs the network or auth\n * runs in the resolver, not here.\n *\n * Each `FileSource` kind gets its own arm. Adding a new shape is a single\n * edit in this file plus a `FileSource` variant in `../types.ts`.\n */\n\nimport type { CloudFile } from \"../../types\";\nimport { classify } from \"../utils/classify\";\nimport { pythonFileInlineUrl, pythonShareUrl } from \"../utils/python-base\";\nimport { createTrackedObjectUrl } from \"../../../../files\";\nimport {\n EPHEMERAL_CAPS,\n EXTERNAL_CAPS,\n PUBLIC_CAPS,\n} from \"../intelligence/access\";\nimport type { FileSource, NormalizedFile } from \"../types\";\n\nconst STREAM_RENDER_TYPES = new Set([\"image\", \"audio\", \"video\", \"document\"]);\n\nexport function normalize(source: FileSource): NormalizedFile {\n switch (source.kind) {\n case \"blob\":\n return fromBlob(source);\n case \"file\":\n return fromFile(source);\n case \"buffer\":\n return fromBuffer(source);\n case \"stream\":\n return fromStream(source);\n case \"data_uri\":\n return fromDataUri(source);\n case \"base64\":\n return fromBase64(source);\n case \"external_url\":\n return fromExternalUrl(source);\n case \"youtube\":\n return fromYouTube(source);\n case \"cloud_file\":\n return fromCloudFile(source.cloudFile, source);\n case \"file_id\":\n return fromFileId(source);\n case \"share_link\":\n return fromShareLink(source);\n case \"public_cdn\":\n return fromPublicCdn(source);\n case \"upload_result\":\n return fromUploadResult(source);\n case \"stream_event\":\n return fromStreamEvent(source);\n }\n}\n\n// ---------------------------------------------------------------------------\n// Ephemeral / raw bytes\n// ---------------------------------------------------------------------------\n\nfunction fromBlob(\n source: Extract<FileSource, { kind: \"blob\" }>,\n): NormalizedFile {\n const url = createTrackedObjectUrl(source.blob);\n const meta = classify({\n fileName: source.fileName,\n mime: source.mime ?? source.blob.type,\n sizeBytes: source.blob.size,\n });\n return {\n url,\n origin: \"ephemeral\",\n capabilities: EPHEMERAL_CAPS,\n meta,\n lifecycle: { refreshable: false, persisted: false },\n scope: {},\n __source: source,\n };\n}\n\nfunction fromFile(\n source: Extract<FileSource, { kind: \"file\" }>,\n): NormalizedFile {\n const url = createTrackedObjectUrl(source.file);\n const meta = classify({\n fileName: source.file.name,\n mime: source.file.type,\n sizeBytes: source.file.size,\n });\n return {\n url,\n origin: \"ephemeral\",\n capabilities: EPHEMERAL_CAPS,\n meta,\n lifecycle: { refreshable: false, persisted: false },\n scope: {},\n __source: source,\n };\n}\n\nfunction fromBuffer(\n source: Extract<FileSource, { kind: \"buffer\" }>,\n): NormalizedFile {\n const blob = bufferToBlob(source.buffer, source.mime);\n const url = createTrackedObjectUrl(blob);\n const meta = classify({\n fileName: source.fileName,\n mime: source.mime,\n sizeBytes: blob.size,\n });\n return {\n url,\n origin: \"ephemeral\",\n capabilities: EPHEMERAL_CAPS,\n meta,\n lifecycle: { refreshable: false, persisted: false },\n scope: {},\n __source: source,\n };\n}\n\nfunction fromStream(\n source: Extract<FileSource, { kind: \"stream\" }>,\n): NormalizedFile {\n // Stream sources can't produce a synchronous URL — output adapters that\n // need bytes will tee the stream in `to-blob.ts`. The normalized form\n // carries no URL at this stage; consumers that need one will be fed a\n // blob URL after the stream is consumed.\n return {\n origin: \"ephemeral\",\n capabilities: EPHEMERAL_CAPS,\n meta: classify({ fileName: source.fileName, mime: source.mime }),\n lifecycle: { refreshable: false, persisted: false },\n scope: {},\n __source: source,\n };\n}\n\nfunction fromDataUri(\n source: Extract<FileSource, { kind: \"data_uri\" }>,\n): NormalizedFile {\n const match = /^data:([^;,]+)(;base64)?,(.+)$/i.exec(source.dataUri);\n const mime = match?.[1];\n const isBase64 = !!match?.[2];\n const payload = match?.[3] ?? \"\";\n return {\n url: source.dataUri,\n base64: isBase64 ? payload : undefined,\n origin: \"ephemeral\",\n capabilities: EPHEMERAL_CAPS,\n meta: classify({ mime }),\n lifecycle: { refreshable: false, persisted: false },\n scope: {},\n __source: source,\n };\n}\n\nfunction fromBase64(\n source: Extract<FileSource, { kind: \"base64\" }>,\n): NormalizedFile {\n return {\n url: `data:${source.mime};base64,${source.base64}`,\n base64: source.base64,\n origin: \"ephemeral\",\n capabilities: EPHEMERAL_CAPS,\n meta: classify({ fileName: source.fileName, mime: source.mime }),\n lifecycle: { refreshable: false, persisted: false },\n scope: {},\n __source: source,\n };\n}\n\n// ---------------------------------------------------------------------------\n// External\n// ---------------------------------------------------------------------------\n\nfunction fromExternalUrl(\n source: Extract<FileSource, { kind: \"external_url\" }>,\n): NormalizedFile {\n return {\n url: source.url,\n origin: \"external\",\n capabilities: EXTERNAL_CAPS,\n meta: classify({\n mime: source.mime,\n fileName: filenameFromUrl(source.url),\n }),\n lifecycle: { refreshable: false, persisted: true },\n scope: {},\n __source: source,\n };\n}\n\nfunction fromYouTube(\n source: Extract<FileSource, { kind: \"youtube\" }>,\n): NormalizedFile {\n return {\n url: source.url,\n youtubeUrl: source.url,\n origin: \"external\",\n capabilities: EXTERNAL_CAPS,\n meta: classify({ mime: \"video/youtube\", fileName: \"youtube.video\" }),\n lifecycle: { refreshable: false, persisted: true },\n scope: {},\n __source: source,\n };\n}\n\n// ---------------------------------------------------------------------------\n// Owned / identifier-bearing\n// ---------------------------------------------------------------------------\n\nexport function fromCloudFile(\n cloudFile: CloudFile,\n source: FileSource,\n): NormalizedFile {\n const meta = classify({\n fileName: cloudFile.fileName,\n mime: cloudFile.mimeType ?? undefined,\n sizeBytes: cloudFile.fileSize ?? undefined,\n checksum: cloudFile.checksum ?? undefined,\n });\n // Every URL flavour is DURABLE now. Public files prefer the permanent\n // CDN URL (cdn_url → canonical url → legacy public_url). Private/shared\n // files bind the server's canonical `url` (the durable\n // `/files/{id}/download?inline=1` route) when the envelope carries it,\n // else build the identical durable URL from the file id — synchronously,\n // no minting round-trip.\n const isPublic = cloudFile.visibility === \"public\";\n const bestUrl = isPublic\n ? (cloudFile.cdnUrl ??\n cloudFile.url ??\n cloudFile.publicUrl ??\n pythonFileInlineUrl(cloudFile.id))\n : (cloudFile.url ??\n cloudFile.publicUrl ??\n pythonFileInlineUrl(cloudFile.id));\n return {\n fileId: cloudFile.id,\n filePath: cloudFile.filePath,\n url: bestUrl,\n origin: cloudFile.visibility === \"public\" ? \"public\" : \"owned\",\n capabilities: {\n canRead: true,\n canEdit: true,\n canShare: true,\n canDelete: true,\n requiresAuth: cloudFile.visibility !== \"public\",\n // Durable route URLs (and CDN/public URLs) are fetch-safe: the\n // python-client attaches Authorization headers to our own routes.\n transportSafeForFetch: true,\n },\n meta,\n lifecycle: {\n refreshable: true,\n persisted: !cloudFile.deletedAt,\n },\n scope: {\n ownerId: cloudFile.ownerId,\n organizationId: cloudFile.organizationId ?? readOrgScope(cloudFile),\n projectId: readProjectScope(cloudFile),\n taskId: readTaskScope(cloudFile),\n },\n derivedFrom: cloudFile.parentFileId\n ? { fileId: cloudFile.parentFileId, kind: cloudFile.derivationKind ?? \"\" }\n : undefined,\n __source: source,\n };\n}\n\nfunction fromFileId(\n source: Extract<FileSource, { kind: \"file_id\" }>,\n): NormalizedFile {\n return {\n fileId: source.fileId,\n // The durable inline URL is a pure function of the id — seed it here so\n // render paths are synchronous. The resolver still hydrates metadata and\n // may replace it with a permanent CDN URL for public files.\n url: pythonFileInlineUrl(source.fileId),\n origin: \"owned\",\n capabilities: {\n canRead: true,\n canEdit: false,\n canShare: false,\n canDelete: false,\n requiresAuth: true,\n transportSafeForFetch: true,\n },\n meta: classify({ mime: source.mime }),\n lifecycle: { refreshable: true, persisted: true },\n scope: {},\n __source: source,\n };\n}\n\n// ---------------------------------------------------------------------------\n// Server-issued URLs\n// ---------------------------------------------------------------------------\n\nfunction fromShareLink(\n source: Extract<FileSource, { kind: \"share_link\" }>,\n): NormalizedFile {\n return {\n shareToken: source.token,\n url: pythonShareUrl(source.token),\n origin: \"public\",\n capabilities: PUBLIC_CAPS,\n meta: classify({ mime: source.mime }),\n lifecycle: { refreshable: true, persisted: true },\n scope: {},\n __source: source,\n };\n}\n\nfunction fromPublicCdn(\n source: Extract<FileSource, { kind: \"public_cdn\" }>,\n): NormalizedFile {\n return {\n fileId: source.fileId,\n url: source.url,\n origin: \"public\",\n capabilities: { ...PUBLIC_CAPS, transportSafeForFetch: true },\n meta: classify({\n mime: source.mime,\n fileName: filenameFromUrl(source.url),\n }),\n lifecycle: { refreshable: !!source.fileId, persisted: true },\n scope: {},\n __source: source,\n };\n}\n\nfunction fromUploadResult(\n source: Extract<FileSource, { kind: \"upload_result\" }>,\n): NormalizedFile {\n const r = source.uploadResult;\n return {\n fileId: r.fileId,\n url: r.url,\n origin: \"owned\",\n capabilities: {\n canRead: true,\n canEdit: !!r.fileId,\n canShare: !!r.fileId,\n canDelete: !!r.fileId,\n requiresAuth: true,\n transportSafeForFetch: true,\n },\n meta: classify({\n mime:\n typeof r.metadata?.mimetype === \"string\"\n ? (r.metadata.mimetype as string)\n : undefined,\n fileName:\n typeof r.metadata?.name === \"string\"\n ? (r.metadata.name as string)\n : undefined,\n }),\n lifecycle: { refreshable: !!r.fileId, persisted: true },\n scope: {},\n __source: source,\n };\n}\n\n// ---------------------------------------------------------------------------\n// Stream events — server-emitted file references arriving mid-stream\n// ---------------------------------------------------------------------------\n\nfunction fromStreamEvent(\n source: Extract<FileSource, { kind: \"stream_event\" }>,\n): NormalizedFile {\n const payload = source.payload as Record<string, unknown> | null;\n if (!payload || typeof payload !== \"object\") {\n return emptyEphemeral(source);\n }\n\n // Two shapes the stream produces:\n // 1. RenderBlock — { type, data: { src }, ... } for type in image/audio/video/document\n // 2. TypedDataPayload — { type: \"image_output\"|\"audio_output\"|\"video_output\", url, mime_type }\n // Both reduce to: a URL + a MIME, with optional fileId.\n const type = String(payload.type ?? \"\");\n let url: string | undefined;\n let mime: string | undefined;\n\n if (\n type === \"image_output\" ||\n type === \"audio_output\" ||\n type === \"video_output\"\n ) {\n url = payload.url as string | undefined;\n mime = payload.mime_type as string | undefined;\n } else if (STREAM_RENDER_TYPES.has(type)) {\n const data = payload.data as Record<string, unknown> | undefined;\n url = (data?.src ?? data?.url) as string | undefined;\n const meta = payload.metadata as Record<string, unknown> | undefined;\n mime = (meta?.mime_type ?? meta?.mime) as string | undefined;\n }\n\n const fileId =\n (payload.file_id as string | undefined) ??\n ((payload.metadata as Record<string, unknown> | undefined)?.file_id as\n string | undefined);\n\n return {\n fileId,\n url,\n origin: fileId ? \"owned\" : \"external\",\n capabilities: {\n canRead: true,\n canEdit: false,\n canShare: false,\n canDelete: false,\n requiresAuth: !!fileId,\n transportSafeForFetch: !fileId,\n },\n meta: classify({ mime, fileName: filenameFromUrl(url ?? \"\") }),\n lifecycle: { refreshable: !!fileId, persisted: true },\n scope: {},\n __source: source,\n };\n}\n\n// ---------------------------------------------------------------------------\n// Helpers\n// ---------------------------------------------------------------------------\n\nfunction filenameFromUrl(url: string): string | undefined {\n if (!url) return undefined;\n try {\n const path = new URL(url, \"https://x\").pathname;\n const last = path.split(\"/\").pop();\n return last && last.length > 0 ? last : undefined;\n } catch {\n return undefined;\n }\n}\n\nfunction emptyEphemeral(source: FileSource): NormalizedFile {\n return {\n origin: \"ephemeral\",\n capabilities: EPHEMERAL_CAPS,\n meta: classify({}),\n lifecycle: { refreshable: false, persisted: false },\n scope: {},\n __source: source,\n };\n}\n\nfunction bufferToBlob(\n buffer: ArrayBuffer | Uint8Array | SharedArrayBuffer,\n mime: string,\n): Blob {\n if (buffer instanceof Uint8Array) {\n const copy = new Uint8Array(buffer.byteLength);\n copy.set(buffer);\n return new Blob([copy.buffer], { type: mime });\n }\n if (buffer instanceof ArrayBuffer) return new Blob([buffer], { type: mime });\n const view = new Uint8Array(buffer);\n const copy = new Uint8Array(view.byteLength);\n copy.set(view);\n return new Blob([copy.buffer], { type: mime });\n}\n\nfunction readOrgScope(file: CloudFile): string | undefined {\n const v = file.metadata?.[\"scope\"];\n if (v && typeof v === \"object\" && !Array.isArray(v)) {\n const orgId = (v as Record<string, unknown>).organization_id;\n if (typeof orgId === \"string\") return orgId;\n }\n return undefined;\n}\n\nfunction readProjectScope(file: CloudFile): string | undefined {\n const v = file.metadata?.[\"scope\"];\n if (v && typeof v === \"object\" && !Array.isArray(v)) {\n const projectId = (v as Record<string, unknown>).project_id;\n if (typeof projectId === \"string\") return projectId;\n }\n return undefined;\n}\n\nfunction readTaskScope(file: CloudFile): string | undefined {\n const v = file.metadata?.[\"scope\"];\n if (v && typeof v === \"object\" && !Array.isArray(v)) {\n const taskId = (v as Record<string, unknown>).task_id;\n if (typeof taskId === \"string\") return taskId;\n }\n return undefined;\n}\n"],"mappings":"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","names":["copy"]}
@@ -0,0 +1,35 @@
1
+ /**
2
+ * features/files/handler/intelligence/access.ts
3
+ *
4
+ * The "can this user access this file?" decision. The handler consults
5
+ * this BEFORE deciding whether a backend failure is "expired URL, refresh"
6
+ * or "no access, reject".
7
+ *
8
+ * Order of checks:
9
+ * 1. owner_id === current user → owned (full caps)
10
+ * 2. visibility === "public" OR public CDN URL → public (read-only)
11
+ * 3. canonical `public.permissions` grant matches user → shared
12
+ * (level-derived caps). Grants are loaded into Redux via `loadPermissions`
13
+ * and passed in here; the authoritative server-side check is
14
+ * `iam.has_access('file', fileId, level)` / `public.has_permission(...)`.
15
+ * 4. file.organizationId is in user's active orgs → check org-level grant
16
+ * 5. share_token is non-revoked → public (read-only by token)
17
+ * 6. external URL → external (read-only, no caps over the file itself)
18
+ * 7. None of the above → unauthorized
19
+ */
20
+ import { type RootState } from "../../host/store.js";
21
+ import type { CloudFile, CloudFilePermission } from "../../types.js";
22
+ import type { FileCapabilities, FileOrigin } from "../types.js";
23
+ export interface AccessDecision {
24
+ origin: FileOrigin;
25
+ capabilities: FileCapabilities;
26
+ }
27
+ /**
28
+ * Decide origin + capabilities for an OWNED-OR-SHARED cld_files row, given
29
+ * the current user from Redux. Public files and external URLs are decided
30
+ * by the input adapter — not here — because they don't need the user.
31
+ */
32
+ export declare function decideForOwnedFile(file: CloudFile, state: RootState, permissions?: CloudFilePermission[]): AccessDecision;
33
+ export declare const PUBLIC_CAPS: FileCapabilities;
34
+ export declare const EXTERNAL_CAPS: FileCapabilities;
35
+ export declare const EPHEMERAL_CAPS: FileCapabilities;
@@ -0,0 +1,85 @@
1
+ import { selectUserId } from "../../host/store.js";
2
+ const READ_ONLY = {
3
+ canRead: true,
4
+ canEdit: false,
5
+ canShare: false,
6
+ canDelete: false,
7
+ requiresAuth: false,
8
+ transportSafeForFetch: true
9
+ };
10
+ const NO_ACCESS = {
11
+ canRead: false,
12
+ canEdit: false,
13
+ canShare: false,
14
+ canDelete: false,
15
+ requiresAuth: true,
16
+ transportSafeForFetch: true
17
+ };
18
+ function fromLevel(level) {
19
+ return {
20
+ canRead: true,
21
+ canEdit: level === "write" || level === "admin",
22
+ canShare: level === "admin",
23
+ canDelete: level === "admin",
24
+ requiresAuth: true,
25
+ transportSafeForFetch: true
26
+ };
27
+ }
28
+ function decideForOwnedFile(file, state, permissions) {
29
+ const userId = selectUserId(state);
30
+ if (userId && file.ownerId === userId) {
31
+ return {
32
+ origin: "owned",
33
+ capabilities: {
34
+ canRead: true,
35
+ canEdit: true,
36
+ canShare: true,
37
+ canDelete: true,
38
+ requiresAuth: true,
39
+ transportSafeForFetch: true
40
+ }
41
+ };
42
+ }
43
+ if (file.visibility === "public") {
44
+ return { origin: "public", capabilities: READ_ONLY };
45
+ }
46
+ if (userId && permissions?.length) {
47
+ const grant = permissions.find(
48
+ (p) => p.granteeType === "user" && p.granteeId === userId
49
+ );
50
+ if (grant) {
51
+ const expired = grant.expiresAt && Date.parse(grant.expiresAt) < Date.now();
52
+ if (!expired) {
53
+ return {
54
+ origin: "shared",
55
+ capabilities: fromLevel(grant.permissionLevel)
56
+ };
57
+ }
58
+ }
59
+ }
60
+ return { origin: "owned", capabilities: NO_ACCESS };
61
+ }
62
+ const PUBLIC_CAPS = READ_ONLY;
63
+ const EXTERNAL_CAPS = {
64
+ canRead: true,
65
+ canEdit: false,
66
+ canShare: false,
67
+ canDelete: false,
68
+ requiresAuth: false,
69
+ transportSafeForFetch: false
70
+ };
71
+ const EPHEMERAL_CAPS = {
72
+ canRead: true,
73
+ canEdit: true,
74
+ canShare: false,
75
+ canDelete: true,
76
+ requiresAuth: false,
77
+ transportSafeForFetch: true
78
+ };
79
+ export {
80
+ EPHEMERAL_CAPS,
81
+ EXTERNAL_CAPS,
82
+ PUBLIC_CAPS,
83
+ decideForOwnedFile
84
+ };
85
+ //# sourceMappingURL=access.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../../../../src/files/engine/handler/intelligence/access.ts"],"sourcesContent":["/**\n * features/files/handler/intelligence/access.ts\n *\n * The \"can this user access this file?\" decision. The handler consults\n * this BEFORE deciding whether a backend failure is \"expired URL, refresh\"\n * or \"no access, reject\".\n *\n * Order of checks:\n * 1. owner_id === current user → owned (full caps)\n * 2. visibility === \"public\" OR public CDN URL → public (read-only)\n * 3. canonical `public.permissions` grant matches user → shared\n * (level-derived caps). Grants are loaded into Redux via `loadPermissions`\n * and passed in here; the authoritative server-side check is\n * `iam.has_access('file', fileId, level)` / `public.has_permission(...)`.\n * 4. file.organizationId is in user's active orgs → check org-level grant\n * 5. share_token is non-revoked → public (read-only by token)\n * 6. external URL → external (read-only, no caps over the file itself)\n * 7. None of the above → unauthorized\n */\n\nimport { selectUserId, type RootState } from \"../../host/store\";\nimport type { CloudFile, CloudFilePermission } from \"../../types\";\nimport type { FileCapabilities, FileOrigin } from \"../types\";\n\nexport interface AccessDecision {\n origin: FileOrigin;\n capabilities: FileCapabilities;\n}\n\nconst READ_ONLY: FileCapabilities = {\n canRead: true,\n canEdit: false,\n canShare: false,\n canDelete: false,\n requiresAuth: false,\n transportSafeForFetch: true,\n};\n\nconst NO_ACCESS: FileCapabilities = {\n canRead: false,\n canEdit: false,\n canShare: false,\n canDelete: false,\n requiresAuth: true,\n transportSafeForFetch: true,\n};\n\nfunction fromLevel(level: \"read\" | \"write\" | \"admin\"): FileCapabilities {\n return {\n canRead: true,\n canEdit: level === \"write\" || level === \"admin\",\n canShare: level === \"admin\",\n canDelete: level === \"admin\",\n requiresAuth: true,\n transportSafeForFetch: true,\n };\n}\n\n/**\n * Decide origin + capabilities for an OWNED-OR-SHARED cld_files row, given\n * the current user from Redux. Public files and external URLs are decided\n * by the input adapter — not here — because they don't need the user.\n */\nexport function decideForOwnedFile(\n file: CloudFile,\n state: RootState,\n permissions?: CloudFilePermission[],\n): AccessDecision {\n const userId = selectUserId(state);\n\n if (userId && file.ownerId === userId) {\n return {\n origin: \"owned\",\n capabilities: {\n canRead: true,\n canEdit: true,\n canShare: true,\n canDelete: true,\n requiresAuth: true,\n transportSafeForFetch: true,\n },\n };\n }\n\n if (file.visibility === \"public\") {\n return { origin: \"public\", capabilities: READ_ONLY };\n }\n\n if (userId && permissions?.length) {\n const grant = permissions.find(\n (p) => p.granteeType === \"user\" && p.granteeId === userId,\n );\n if (grant) {\n const expired =\n grant.expiresAt && Date.parse(grant.expiresAt) < Date.now();\n if (!expired) {\n return {\n origin: \"shared\",\n capabilities: fromLevel(grant.permissionLevel),\n };\n }\n }\n }\n\n return { origin: \"owned\", capabilities: NO_ACCESS };\n}\n\nexport const PUBLIC_CAPS = READ_ONLY;\nexport const EXTERNAL_CAPS: FileCapabilities = {\n canRead: true,\n canEdit: false,\n canShare: false,\n canDelete: false,\n requiresAuth: false,\n transportSafeForFetch: false,\n};\nexport const EPHEMERAL_CAPS: FileCapabilities = {\n canRead: true,\n canEdit: true,\n canShare: false,\n canDelete: true,\n requiresAuth: false,\n transportSafeForFetch: true,\n};\n"],"mappings":"AAoBA,SAAS,oBAAoC;AAS7C,MAAM,YAA8B;AAAA,EAClC,SAAS;AAAA,EACT,SAAS;AAAA,EACT,UAAU;AAAA,EACV,WAAW;AAAA,EACX,cAAc;AAAA,EACd,uBAAuB;AACzB;AAEA,MAAM,YAA8B;AAAA,EAClC,SAAS;AAAA,EACT,SAAS;AAAA,EACT,UAAU;AAAA,EACV,WAAW;AAAA,EACX,cAAc;AAAA,EACd,uBAAuB;AACzB;AAEA,SAAS,UAAU,OAAqD;AACtE,SAAO;AAAA,IACL,SAAS;AAAA,IACT,SAAS,UAAU,WAAW,UAAU;AAAA,IACxC,UAAU,UAAU;AAAA,IACpB,WAAW,UAAU;AAAA,IACrB,cAAc;AAAA,IACd,uBAAuB;AAAA,EACzB;AACF;AAOO,SAAS,mBACd,MACA,OACA,aACgB;AAChB,QAAM,SAAS,aAAa,KAAK;AAEjC,MAAI,UAAU,KAAK,YAAY,QAAQ;AACrC,WAAO;AAAA,MACL,QAAQ;AAAA,MACR,cAAc;AAAA,QACZ,SAAS;AAAA,QACT,SAAS;AAAA,QACT,UAAU;AAAA,QACV,WAAW;AAAA,QACX,cAAc;AAAA,QACd,uBAAuB;AAAA,MACzB;AAAA,IACF;AAAA,EACF;AAEA,MAAI,KAAK,eAAe,UAAU;AAChC,WAAO,EAAE,QAAQ,UAAU,cAAc,UAAU;AAAA,EACrD;AAEA,MAAI,UAAU,aAAa,QAAQ;AACjC,UAAM,QAAQ,YAAY;AAAA,MACxB,CAAC,MAAM,EAAE,gBAAgB,UAAU,EAAE,cAAc;AAAA,IACrD;AACA,QAAI,OAAO;AACT,YAAM,UACJ,MAAM,aAAa,KAAK,MAAM,MAAM,SAAS,IAAI,KAAK,IAAI;AAC5D,UAAI,CAAC,SAAS;AACZ,eAAO;AAAA,UACL,QAAQ;AAAA,UACR,cAAc,UAAU,MAAM,eAAe;AAAA,QAC/C;AAAA,MACF;AAAA,IACF;AAAA,EACF;AAEA,SAAO,EAAE,QAAQ,SAAS,cAAc,UAAU;AACpD;AAEO,MAAM,cAAc;AACpB,MAAM,gBAAkC;AAAA,EAC7C,SAAS;AAAA,EACT,SAAS;AAAA,EACT,UAAU;AAAA,EACV,WAAW;AAAA,EACX,cAAc;AAAA,EACd,uBAAuB;AACzB;AACO,MAAM,iBAAmC;AAAA,EAC9C,SAAS;AAAA,EACT,SAAS;AAAA,EACT,UAAU;AAAA,EACV,WAAW;AAAA,EACX,cAAc;AAAA,EACd,uBAAuB;AACzB;","names":[]}
@@ -0,0 +1,21 @@
1
+ /**
2
+ * features/files/handler/intelligence/magic-bytes.ts
3
+ *
4
+ * MIME sniffing from the first ~16 bytes of a file. Used when a source
5
+ * arrives with no Content-Type and no extension (rare but happens with
6
+ * paste-from-clipboard, raw buffers, and stream events).
7
+ *
8
+ * The big eight image formats plus PDF cover ~99% of real input. Anything
9
+ * else falls through to `application/octet-stream` and the resolver
10
+ * defers to the file extension or the FILE_TYPES registry.
11
+ */
12
+ /**
13
+ * Sniff a MIME from the leading bytes. Returns `null` if no rule matches —
14
+ * the caller decides whether to fall back to extension or octet-stream.
15
+ */
16
+ export declare function sniffMime(bytes: Uint8Array): string | null;
17
+ /**
18
+ * Read the first N bytes of a Blob/File without loading the whole thing.
19
+ * Sniff window is 32 bytes — enough for every rule above.
20
+ */
21
+ export declare function sniffMimeFromBlob(blob: Blob): Promise<string | null>;
@@ -0,0 +1,67 @@
1
+ const MAGIC_TABLE = [
2
+ { mime: "image/png", prefix: [137, 80, 78, 71] },
3
+ { mime: "image/jpeg", prefix: [255, 216, 255] },
4
+ { mime: "image/gif", prefix: [71, 73, 70, 56] },
5
+ { mime: "image/webp", prefix: [82, 73, 70, 70], match: isWebp },
6
+ { mime: "image/bmp", prefix: [66, 77] },
7
+ { mime: "image/x-icon", prefix: [0, 0, 1, 0] },
8
+ { mime: "image/avif", prefix: [], match: isAvif },
9
+ { mime: "image/heic", prefix: [], match: isHeic },
10
+ { mime: "application/pdf", prefix: [37, 80, 68, 70] },
11
+ { mime: "application/zip", prefix: [80, 75, 3, 4] },
12
+ { mime: "video/mp4", prefix: [], match: isMp4 },
13
+ { mime: "video/webm", prefix: [26, 69, 223, 163] },
14
+ { mime: "audio/mpeg", prefix: [255, 251] },
15
+ { mime: "audio/mpeg", prefix: [73, 68, 51] },
16
+ { mime: "audio/wav", prefix: [82, 73, 70, 70], match: isWav },
17
+ { mime: "audio/ogg", prefix: [79, 103, 103, 83] },
18
+ { mime: "audio/flac", prefix: [102, 76, 97, 67] }
19
+ ];
20
+ function startsWith(bytes, prefix, offset = 0) {
21
+ if (bytes.length < offset + prefix.length) return false;
22
+ for (let i = 0; i < prefix.length; i++) {
23
+ if (bytes[offset + i] !== prefix[i]) return false;
24
+ }
25
+ return true;
26
+ }
27
+ function isWebp(bytes) {
28
+ return bytes.length >= 12 && startsWith(bytes, [82, 73, 70, 70]) && startsWith(bytes, [87, 69, 66, 80], 8);
29
+ }
30
+ function isWav(bytes) {
31
+ return bytes.length >= 12 && startsWith(bytes, [82, 73, 70, 70]) && startsWith(bytes, [87, 65, 86, 69], 8);
32
+ }
33
+ function isMp4(bytes) {
34
+ return bytes.length >= 12 && startsWith(bytes, [102, 116, 121, 112], 4);
35
+ }
36
+ function isAvif(bytes) {
37
+ if (bytes.length < 12) return false;
38
+ if (!startsWith(bytes, [102, 116, 121, 112], 4)) return false;
39
+ const brand = String.fromCharCode(bytes[8], bytes[9], bytes[10], bytes[11]);
40
+ return brand === "avif" || brand === "avis";
41
+ }
42
+ function isHeic(bytes) {
43
+ if (bytes.length < 12) return false;
44
+ if (!startsWith(bytes, [102, 116, 121, 112], 4)) return false;
45
+ const brand = String.fromCharCode(bytes[8], bytes[9], bytes[10], bytes[11]);
46
+ return brand === "heic" || brand === "heix" || brand === "heim" || brand === "heis";
47
+ }
48
+ function sniffMime(bytes) {
49
+ for (const rule of MAGIC_TABLE) {
50
+ if (rule.match) {
51
+ if (rule.match(bytes)) return rule.mime;
52
+ continue;
53
+ }
54
+ if (startsWith(bytes, rule.prefix, rule.offset)) return rule.mime;
55
+ }
56
+ return null;
57
+ }
58
+ async function sniffMimeFromBlob(blob) {
59
+ const slice = blob.slice(0, 32);
60
+ const buffer = await slice.arrayBuffer();
61
+ return sniffMime(new Uint8Array(buffer));
62
+ }
63
+ export {
64
+ sniffMime,
65
+ sniffMimeFromBlob
66
+ };
67
+ //# sourceMappingURL=magic-bytes.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../../../../src/files/engine/handler/intelligence/magic-bytes.ts"],"sourcesContent":["/**\n * features/files/handler/intelligence/magic-bytes.ts\n *\n * MIME sniffing from the first ~16 bytes of a file. Used when a source\n * arrives with no Content-Type and no extension (rare but happens with\n * paste-from-clipboard, raw buffers, and stream events).\n *\n * The big eight image formats plus PDF cover ~99% of real input. Anything\n * else falls through to `application/octet-stream` and the resolver\n * defers to the file extension or the FILE_TYPES registry.\n */\n\nconst MAGIC_TABLE: Array<{\n mime: string;\n prefix: number[];\n offset?: number;\n match?: (bytes: Uint8Array) => boolean;\n}> = [\n { mime: \"image/png\", prefix: [0x89, 0x50, 0x4e, 0x47] },\n { mime: \"image/jpeg\", prefix: [0xff, 0xd8, 0xff] },\n { mime: \"image/gif\", prefix: [0x47, 0x49, 0x46, 0x38] },\n { mime: \"image/webp\", prefix: [0x52, 0x49, 0x46, 0x46], match: isWebp },\n { mime: \"image/bmp\", prefix: [0x42, 0x4d] },\n { mime: \"image/x-icon\", prefix: [0x00, 0x00, 0x01, 0x00] },\n { mime: \"image/avif\", prefix: [], match: isAvif },\n { mime: \"image/heic\", prefix: [], match: isHeic },\n { mime: \"application/pdf\", prefix: [0x25, 0x50, 0x44, 0x46] },\n { mime: \"application/zip\", prefix: [0x50, 0x4b, 0x03, 0x04] },\n { mime: \"video/mp4\", prefix: [], match: isMp4 },\n { mime: \"video/webm\", prefix: [0x1a, 0x45, 0xdf, 0xa3] },\n { mime: \"audio/mpeg\", prefix: [0xff, 0xfb] },\n { mime: \"audio/mpeg\", prefix: [0x49, 0x44, 0x33] },\n { mime: \"audio/wav\", prefix: [0x52, 0x49, 0x46, 0x46], match: isWav },\n { mime: \"audio/ogg\", prefix: [0x4f, 0x67, 0x67, 0x53] },\n { mime: \"audio/flac\", prefix: [0x66, 0x4c, 0x61, 0x43] },\n];\n\nfunction startsWith(bytes: Uint8Array, prefix: number[], offset = 0): boolean {\n if (bytes.length < offset + prefix.length) return false;\n for (let i = 0; i < prefix.length; i++) {\n if (bytes[offset + i] !== prefix[i]) return false;\n }\n return true;\n}\n\nfunction isWebp(bytes: Uint8Array): boolean {\n return (\n bytes.length >= 12 &&\n startsWith(bytes, [0x52, 0x49, 0x46, 0x46]) &&\n startsWith(bytes, [0x57, 0x45, 0x42, 0x50], 8)\n );\n}\n\nfunction isWav(bytes: Uint8Array): boolean {\n return (\n bytes.length >= 12 &&\n startsWith(bytes, [0x52, 0x49, 0x46, 0x46]) &&\n startsWith(bytes, [0x57, 0x41, 0x56, 0x45], 8)\n );\n}\n\nfunction isMp4(bytes: Uint8Array): boolean {\n return bytes.length >= 12 && startsWith(bytes, [0x66, 0x74, 0x79, 0x70], 4);\n}\n\nfunction isAvif(bytes: Uint8Array): boolean {\n if (bytes.length < 12) return false;\n if (!startsWith(bytes, [0x66, 0x74, 0x79, 0x70], 4)) return false;\n const brand = String.fromCharCode(bytes[8], bytes[9], bytes[10], bytes[11]);\n return brand === \"avif\" || brand === \"avis\";\n}\n\nfunction isHeic(bytes: Uint8Array): boolean {\n if (bytes.length < 12) return false;\n if (!startsWith(bytes, [0x66, 0x74, 0x79, 0x70], 4)) return false;\n const brand = String.fromCharCode(bytes[8], bytes[9], bytes[10], bytes[11]);\n return brand === \"heic\" || brand === \"heix\" || brand === \"heim\" || brand === \"heis\";\n}\n\n/**\n * Sniff a MIME from the leading bytes. Returns `null` if no rule matches —\n * the caller decides whether to fall back to extension or octet-stream.\n */\nexport function sniffMime(bytes: Uint8Array): string | null {\n for (const rule of MAGIC_TABLE) {\n if (rule.match) {\n if (rule.match(bytes)) return rule.mime;\n continue;\n }\n if (startsWith(bytes, rule.prefix, rule.offset)) return rule.mime;\n }\n return null;\n}\n\n/**\n * Read the first N bytes of a Blob/File without loading the whole thing.\n * Sniff window is 32 bytes — enough for every rule above.\n */\nexport async function sniffMimeFromBlob(blob: Blob): Promise<string | null> {\n const slice = blob.slice(0, 32);\n const buffer = await slice.arrayBuffer();\n return sniffMime(new Uint8Array(buffer));\n}\n"],"mappings":"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","names":[]}