@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.
Files changed (185) hide show
  1. package/CHANGELOG.md +15 -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/package.json +393 -9
@@ -0,0 +1,162 @@
1
+ import {
2
+ deleteEntriesForFile,
3
+ clearForUser,
4
+ putEntry as putIdbEntry
5
+ } from "../cache/idb-store.js";
6
+ import { shouldPersistInIdb } from "../cache/policy.js";
7
+ import {
8
+ postBlobCacheClearUser,
9
+ postBlobCacheInvalidate
10
+ } from "../cache/register-service-worker.js";
11
+ let identityUserId = null;
12
+ function setBlobCacheIdentity(userId) {
13
+ identityUserId = userId;
14
+ }
15
+ const DEFAULT_BUDGET_BYTES = 250 * 1024 * 1024;
16
+ const cache = /* @__PURE__ */ new Map();
17
+ let totalBytes = 0;
18
+ let budgetBytes = DEFAULT_BUDGET_BYTES;
19
+ function getCached(fileId, version) {
20
+ const entry = cache.get(fileId);
21
+ if (!entry) return null;
22
+ if (version != null) {
23
+ if (entry.version != null && entry.version !== version) return null;
24
+ entry.version = version;
25
+ }
26
+ cache.delete(fileId);
27
+ entry.lastAccessed = Date.now();
28
+ cache.set(fileId, entry);
29
+ return entry;
30
+ }
31
+ async function hydrateFromIdb(fileId, version) {
32
+ if (!identityUserId) return null;
33
+ try {
34
+ const { openBlobCacheDb } = await import("../cache/idb-store.js");
35
+ const db = await openBlobCacheDb();
36
+ if (!db) return null;
37
+ const matches = await db.blobs.where("userId").equals(identityUserId).and(
38
+ (e) => e.fileId === fileId && (version == null || e.version === version)
39
+ ).toArray();
40
+ if (matches.length === 0) return null;
41
+ const winner = matches.reduce(
42
+ (best, candidate) => candidate.lastAccessedAt > best.lastAccessedAt ? candidate : best,
43
+ matches[0]
44
+ );
45
+ const objectUrl = URL.createObjectURL(winner.blob);
46
+ setCachedMemoryOnly(fileId, winner.blob, objectUrl, winner.version ?? null);
47
+ return cache.get(fileId) ?? null;
48
+ } catch {
49
+ return null;
50
+ }
51
+ }
52
+ function setCached(fileId, blob, url, meta) {
53
+ setCachedMemoryOnly(fileId, blob, url, meta?.version ?? null);
54
+ const mime = meta?.mimeType ?? blob.type ?? "application/octet-stream";
55
+ if (identityUserId && shouldPersistInIdb(mime, blob.size)) {
56
+ const version = meta?.version ?? null;
57
+ const checksum = meta?.checksum ?? null;
58
+ const versionSegment = version != null ? String(version) : "current";
59
+ const checksumSegment = checksum ?? "unknown";
60
+ void putIdbEntry({
61
+ key: `${identityUserId}:${fileId}:${versionSegment}:${checksumSegment}`,
62
+ userId: identityUserId,
63
+ fileId,
64
+ version,
65
+ checksum,
66
+ mimeType: mime,
67
+ bytes: blob.size,
68
+ blob,
69
+ etag: checksum,
70
+ fetchedAt: Date.now(),
71
+ lastAccessedAt: Date.now(),
72
+ source: "files-download"
73
+ });
74
+ }
75
+ }
76
+ function setCachedMemoryOnly(fileId, blob, url, version) {
77
+ const existing = cache.get(fileId);
78
+ if (existing) {
79
+ URL.revokeObjectURL(existing.url);
80
+ totalBytes -= existing.bytes;
81
+ cache.delete(fileId);
82
+ }
83
+ const entry = {
84
+ blob,
85
+ url,
86
+ bytes: blob.size,
87
+ lastAccessed: Date.now(),
88
+ version
89
+ };
90
+ cache.set(fileId, entry);
91
+ totalBytes += entry.bytes;
92
+ while (totalBytes > budgetBytes && cache.size > 1) {
93
+ const oldestKey = cache.keys().next().value;
94
+ if (!oldestKey || oldestKey === fileId) break;
95
+ const oldest = cache.get(oldestKey);
96
+ if (oldest) {
97
+ URL.revokeObjectURL(oldest.url);
98
+ totalBytes -= oldest.bytes;
99
+ }
100
+ cache.delete(oldestKey);
101
+ }
102
+ }
103
+ function invalidate(fileId) {
104
+ const entry = cache.get(fileId);
105
+ if (entry) {
106
+ URL.revokeObjectURL(entry.url);
107
+ totalBytes -= entry.bytes;
108
+ cache.delete(fileId);
109
+ }
110
+ if (identityUserId) {
111
+ void deleteEntriesForFile(identityUserId, fileId);
112
+ }
113
+ void postBlobCacheInvalidate(fileId);
114
+ }
115
+ function cachedVersion(fileId) {
116
+ return cache.get(fileId)?.version ?? null;
117
+ }
118
+ function replaceWithSavedBytes(fileId, blob, version) {
119
+ invalidate(fileId);
120
+ try {
121
+ setCached(fileId, blob, URL.createObjectURL(blob), {
122
+ mimeType: blob.type,
123
+ version
124
+ });
125
+ } catch (err) {
126
+ console.warn("[blob-cache] saved bytes not cached; the next view downloads them", err);
127
+ }
128
+ }
129
+ function invalidateAll(userId) {
130
+ for (const entry of cache.values()) {
131
+ URL.revokeObjectURL(entry.url);
132
+ }
133
+ cache.clear();
134
+ totalBytes = 0;
135
+ if (userId) {
136
+ void clearForUser(userId);
137
+ void postBlobCacheClearUser(userId);
138
+ }
139
+ }
140
+ function getCacheStats() {
141
+ return {
142
+ entryCount: cache.size,
143
+ totalBytes,
144
+ budgetBytes
145
+ };
146
+ }
147
+ function setBudgetBytes(bytes) {
148
+ budgetBytes = Math.max(0, bytes);
149
+ }
150
+ export {
151
+ cachedVersion,
152
+ getCacheStats,
153
+ getCached,
154
+ hydrateFromIdb,
155
+ invalidate,
156
+ invalidateAll,
157
+ replaceWithSavedBytes,
158
+ setBlobCacheIdentity,
159
+ setBudgetBytes,
160
+ setCached
161
+ };
162
+ //# sourceMappingURL=blob-cache.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../../../src/files/engine/hooks/blob-cache.ts"],"sourcesContent":["/**\n * features/files/hooks/blob-cache.ts\n *\n * Layer 1 (in-memory LRU) of the 3-tier byte cache. Survives React mount /\n * unmount cycles within a session. Without it, closing and reopening a\n * 10MB PDF re-fetches the whole blob over the wire — useFileBlob keeps\n * its blob in component state, so unmount = `URL.revokeObjectURL` +\n * lost bytes.\n *\n * 3-tier read path (see features/files/cache/):\n * 1. In-memory LRU (this module) — synchronous, session\n * 2. IndexedDB (features/files/cache/idb-store) — persistent, async\n * 3. Network — Python /files/{id}/download\n *\n * Writes populate both tiers when the MIME policy permits IDB persistence\n * (PDFs, ≤50 MB video, ≤100 MB audio, images, etc. — see cache/policy.ts).\n *\n * Design:\n * - Single `Map<fileId, CacheEntry>`. Map iteration order = insertion\n * order, so re-inserting on a cache hit gives us LRU for free.\n * - Capped by total bytes (default 250 MB). When inserts push past\n * the cap, oldest entries are evicted and their object URLs revoked.\n * - The cache OWNS the object URL. `useFileBlob` reads `entry.url`\n * directly and does NOT revoke on unmount — only the cache itself\n * revokes, on eviction or explicit invalidation.\n *\n * Invalidation hooks:\n * - `invalidate(fileId)` — single file. Used by upload-version /\n * restore-version / delete thunks + realtime middleware on\n * cross-device version inserts. Drops from BOTH tiers.\n * - `invalidateAll()` — for sign-out / identity swap. Drops in-memory\n * immediately; pass a userId to also wipe IDB for that user.\n *\n * Why not Redux: Blobs are not serialisable; storing them in the slice\n * would trigger RTK serializableCheck warnings on every dispatch and\n * the full blob would be passed through every middleware. A module-\n * level Map is the right primitive for this.\n */\n\nimport {\n deleteEntriesForFile,\n clearForUser,\n getEntry as getIdbEntry,\n putEntry as putIdbEntry,\n type BlobCacheEntry as IdbBlobCacheEntry,\n} from \"../cache/idb-store\";\nimport { shouldPersistInIdb } from \"../cache/policy\";\nimport {\n postBlobCacheClearUser,\n postBlobCacheInvalidate,\n} from \"../cache/register-service-worker\";\n\nlet identityUserId: string | null = null;\n\n/**\n * Stamp every subsequent put/invalidate with the current user. Called\n * from the sign-in flow (after Supabase session resolves). Without this\n * IDB entries are written under an empty userId — readable but not\n * scoped, which would leak across user swaps. Memory LRU is unaffected.\n */\nexport function setBlobCacheIdentity(userId: string | null): void {\n identityUserId = userId;\n}\n\nconst DEFAULT_BUDGET_BYTES = 250 * 1024 * 1024; // 250 MB total\n\ninterface CacheEntry {\n blob: Blob;\n /** `blob:`-scheme URL — owned by the cache; revoked on eviction. */\n url: string;\n /** `blob.size` cached so eviction math doesn't need to re-read. */\n bytes: number;\n /**\n * The file version these bytes belong to; null when it was not known at\n * fetch time (stamped by the first read that knows it). A read for a\n * different version is a miss — the bytes are refetched only when the\n * version changes.\n */\n version: number | null;\n /** Last-access timestamp (ms). Updated on cache hits. */\n lastAccessed: number;\n}\n\nconst cache = new Map<string, CacheEntry>();\nlet totalBytes = 0;\nlet budgetBytes = DEFAULT_BUDGET_BYTES;\n\n/**\n * Read a cached entry. Touches the LRU position so re-accesses keep\n * the entry alive. Returns `null` on miss — and when `version` is given and\n * the entry holds a different version's bytes.\n */\nexport function getCached(\n fileId: string,\n version?: number | null,\n): CacheEntry | null {\n const entry = cache.get(fileId);\n if (!entry) return null;\n if (version != null) {\n if (entry.version != null && entry.version !== version) return null;\n entry.version = version;\n }\n // Bump LRU position by re-inserting at the end of the iteration order.\n cache.delete(fileId);\n entry.lastAccessed = Date.now();\n cache.set(fileId, entry);\n return entry;\n}\n\n/**\n * Hydrate the in-memory tier from IndexedDB for a single fileId. Returns\n * the hydrated entry on hit, `null` on miss or when IDB is unavailable.\n *\n * Use this in `useFileBlob`'s effect when the in-memory LRU misses but\n * we want to avoid a network round-trip if the bytes persisted from a\n * previous session.\n */\nexport async function hydrateFromIdb(\n fileId: string,\n version?: number | null,\n): Promise<CacheEntry | null> {\n if (!identityUserId) return null;\n try {\n // Read the most recent version's entry. Cache keys embed the version\n // suffix, so we scan with a prefix match via the deleteEntriesForFile\n // family. For now we attempt the \"current\" key shape; the SW layer\n // (Phase 2) will register canonical (url → key) mappings the page can\n // consult before this read.\n // FIRST-CUT: try the canonical \"current\" key shape with a wildcard\n // checksum. The IDB store doesn't support prefix queries directly, so\n // we fall back to scanning the user's entries for a matching fileId.\n // This stays O(entries-per-user) which is fine for the realistic upper\n // bound (~hundreds of cached PDFs).\n const { openBlobCacheDb } = await import(\"../cache/idb-store\");\n const db = await openBlobCacheDb();\n if (!db) return null;\n const matches = await db.blobs\n .where(\"userId\")\n .equals(identityUserId)\n .and(\n (e: IdbBlobCacheEntry) =>\n e.fileId === fileId && (version == null || e.version === version),\n )\n .toArray();\n if (matches.length === 0) return null;\n // Prefer the entry with the most recent `lastAccessedAt`.\n const winner = matches.reduce<IdbBlobCacheEntry>(\n (best, candidate) =>\n candidate.lastAccessedAt > best.lastAccessedAt ? candidate : best,\n matches[0],\n );\n // Promote into the in-memory tier so the next read is synchronous.\n const objectUrl = URL.createObjectURL(winner.blob);\n setCachedMemoryOnly(fileId, winner.blob, objectUrl, winner.version ?? null);\n return cache.get(fileId) ?? null;\n } catch {\n return null;\n }\n}\n\n/**\n * Insert (or overwrite) a cached entry. The cache takes ownership of\n * `blob` + `url` — callers should NOT revoke the URL themselves.\n *\n * Writes the entry into the in-memory LRU AND (when the MIME policy\n * permits — see `features/files/cache/policy.ts`) into IndexedDB so the\n * bytes survive page reloads.\n *\n * If a previous entry for the same fileId exists, its URL is revoked\n * and the entry is replaced. After insert, the cache evicts oldest\n * entries until the total byte size is under the budget.\n */\nexport function setCached(\n fileId: string,\n blob: Blob,\n url: string,\n meta?: { mimeType?: string; version?: number | null; checksum?: string | null },\n): void {\n setCachedMemoryOnly(fileId, blob, url, meta?.version ?? null);\n // Best-effort write to IDB. The promise is intentionally not awaited —\n // the caller's render path is already complete; IDB persistence is a\n // background side-effect that's safe to fail (the in-memory tier is\n // already populated).\n const mime = meta?.mimeType ?? blob.type ?? \"application/octet-stream\";\n if (identityUserId && shouldPersistInIdb(mime, blob.size)) {\n const version = meta?.version ?? null;\n const checksum = meta?.checksum ?? null;\n const versionSegment = version != null ? String(version) : \"current\";\n const checksumSegment = checksum ?? \"unknown\";\n void putIdbEntry({\n key: `${identityUserId}:${fileId}:${versionSegment}:${checksumSegment}`,\n userId: identityUserId,\n fileId,\n version,\n checksum,\n mimeType: mime,\n bytes: blob.size,\n blob,\n etag: checksum,\n fetchedAt: Date.now(),\n lastAccessedAt: Date.now(),\n source: \"files-download\",\n });\n }\n}\n\nfunction setCachedMemoryOnly(\n fileId: string,\n blob: Blob,\n url: string,\n version: number | null,\n): void {\n // Replace any existing entry — must revoke the old URL.\n const existing = cache.get(fileId);\n if (existing) {\n URL.revokeObjectURL(existing.url);\n totalBytes -= existing.bytes;\n cache.delete(fileId);\n }\n\n const entry: CacheEntry = {\n blob,\n url,\n bytes: blob.size,\n lastAccessed: Date.now(),\n version,\n };\n cache.set(fileId, entry);\n totalBytes += entry.bytes;\n\n // Evict oldest entries until the cache is under budget. Map iteration\n // order is insertion order, so the oldest LRU entry is the first key.\n while (totalBytes > budgetBytes && cache.size > 1) {\n const oldestKey = cache.keys().next().value as string | undefined;\n if (!oldestKey || oldestKey === fileId) break;\n const oldest = cache.get(oldestKey);\n if (oldest) {\n URL.revokeObjectURL(oldest.url);\n totalBytes -= oldest.bytes;\n }\n cache.delete(oldestKey);\n }\n}\n\n/**\n * Drop a single file's cached entry from both tiers. Use this from any\n * code path that makes the cached bytes stale: new-version upload,\n * restore-version, hard-delete, realtime-broadcast cross-device version\n * insert.\n */\nexport function invalidate(fileId: string): void {\n const entry = cache.get(fileId);\n if (entry) {\n URL.revokeObjectURL(entry.url);\n totalBytes -= entry.bytes;\n cache.delete(fileId);\n }\n // Fire-and-forget: clear every version of this file in IDB AND tell\n // the Service Worker (if registered) to drop its mirror copy of the\n // same fileId. Three tiers, one invalidate() call.\n if (identityUserId) {\n void deleteEntriesForFile(identityUserId, fileId);\n }\n void postBlobCacheInvalidate(fileId);\n}\n\n/** The version the cached bytes belong to (null: unknown or not cached). No LRU touch. */\nexport function cachedVersion(fileId: string): number | null {\n return cache.get(fileId)?.version ?? null;\n}\n\n/**\n * A save wrote `blob` as version `version` of this file: drop every older\n * copy (all tiers) and cache the saved bytes as that version, so every\n * viewer shows the new version without downloading what this tab just\n * uploaded.\n */\nexport function replaceWithSavedBytes(\n fileId: string,\n blob: Blob,\n version: number,\n): void {\n invalidate(fileId);\n try {\n setCached(fileId, blob, URL.createObjectURL(blob), {\n mimeType: blob.type,\n version,\n });\n } catch (err) {\n // The save itself landed; only the local copy could not be kept, so the\n // next viewer downloads the new version instead. Say so.\n console.warn(\"[blob-cache] saved bytes not cached; the next view downloads them\", err);\n }\n}\n\n/**\n * Drop every in-memory entry. Use on sign-out / identity swap so the\n * next user can never see the previous user's blob URLs lingering in\n * memory. When `userId` is provided, also clears IDB for that user.\n */\nexport function invalidateAll(userId?: string): void {\n for (const entry of cache.values()) {\n URL.revokeObjectURL(entry.url);\n }\n cache.clear();\n totalBytes = 0;\n if (userId) {\n void clearForUser(userId);\n void postBlobCacheClearUser(userId);\n }\n}\n\n/**\n * Test/debug helper. Useful for diagnostic pages that want to render\n * \"blob cache: 3 files, 47 MB / 250 MB\".\n */\nexport function getCacheStats(): {\n entryCount: number;\n totalBytes: number;\n budgetBytes: number;\n} {\n return {\n entryCount: cache.size,\n totalBytes,\n budgetBytes,\n };\n}\n\n/**\n * Dev/test only — change the size budget at runtime. The default is\n * 250 MB (DEFAULT_BUDGET_BYTES).\n */\nexport function setBudgetBytes(bytes: number): void {\n budgetBytes = Math.max(0, bytes);\n}\n"],"mappings":"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","names":[]}
@@ -0,0 +1,28 @@
1
+ /**
2
+ * features/files/hooks/office-extraction-cache.ts
3
+ *
4
+ * Module-level cache of server-side Office extractions (docx/pptx → markdown),
5
+ * keyed by fileId — the OfficePreview analogue of `blob-cache.ts`. Lives
6
+ * OUTSIDE the lazy previewer chunk so the Redux thunks and the realtime
7
+ * middleware can invalidate it (new version upload / restore / delete)
8
+ * without importing the react-markdown previewer graph.
9
+ *
10
+ * Payloads are small (markdown text), so a simple insertion-capped Map is
11
+ * enough — no LRU, no byte accounting.
12
+ */
13
+ import { type OfficeExtraction, type OfficeFileRef } from "../api/office.js";
14
+ /** Cached-or-fetched extraction, deduplicated across simultaneous mounts. */
15
+ export declare function getOfficeExtraction(fileId: string): Promise<OfficeExtraction>;
16
+ /** Synchronous cache peek — lets the previewer render instantly on remount. */
17
+ export declare function peekOfficeExtraction(fileId: string): OfficeExtraction | null;
18
+ /**
19
+ * Cached-or-created PDF derivative of an Office file (server-side LibreOffice
20
+ * lane, idempotent per source revision) — powers the visual slides/pages
21
+ * preview. Deduplicated across simultaneous mounts.
22
+ */
23
+ export declare function getOfficePdf(fileId: string): Promise<OfficeFileRef>;
24
+ /** Synchronous PDF-ref peek — lets the visual previewer render instantly. */
25
+ export declare function peekOfficePdf(fileId: string): OfficeFileRef | null;
26
+ /** Drop the cached extraction AND pdf ref — call wherever the blob cache is
27
+ * invalidated (new version upload, restore, delete, realtime update). */
28
+ export declare function invalidateOfficeExtraction(fileId: string): void;
@@ -0,0 +1,67 @@
1
+ import {
2
+ convertOfficeToPdf,
3
+ extractOfficeMarkdown
4
+ } from "../api/office.js";
5
+ const extractionCache = /* @__PURE__ */ new Map();
6
+ const CACHE_MAX_ENTRIES = 40;
7
+ const inflight = /* @__PURE__ */ new Map();
8
+ const pdfRefCache = /* @__PURE__ */ new Map();
9
+ const pdfInflight = /* @__PURE__ */ new Map();
10
+ function getOfficeExtraction(fileId) {
11
+ const cached = extractionCache.get(fileId);
12
+ if (cached) return Promise.resolve(cached);
13
+ let p = inflight.get(fileId);
14
+ if (!p) {
15
+ p = extractOfficeMarkdown(fileId).then((result) => {
16
+ if (extractionCache.size >= CACHE_MAX_ENTRIES) {
17
+ const oldest = extractionCache.keys().next().value;
18
+ if (oldest !== void 0) extractionCache.delete(oldest);
19
+ }
20
+ extractionCache.set(fileId, result);
21
+ return result;
22
+ });
23
+ inflight.set(fileId, p);
24
+ void p.catch(() => void 0).finally(() => {
25
+ if (inflight.get(fileId) === p) inflight.delete(fileId);
26
+ });
27
+ }
28
+ return p;
29
+ }
30
+ function peekOfficeExtraction(fileId) {
31
+ return extractionCache.get(fileId) ?? null;
32
+ }
33
+ function getOfficePdf(fileId) {
34
+ const cached = pdfRefCache.get(fileId);
35
+ if (cached) return Promise.resolve(cached);
36
+ let p = pdfInflight.get(fileId);
37
+ if (!p) {
38
+ p = convertOfficeToPdf(fileId).then((result) => {
39
+ if (pdfRefCache.size >= CACHE_MAX_ENTRIES) {
40
+ const oldest = pdfRefCache.keys().next().value;
41
+ if (oldest !== void 0) pdfRefCache.delete(oldest);
42
+ }
43
+ pdfRefCache.set(fileId, result);
44
+ return result;
45
+ });
46
+ pdfInflight.set(fileId, p);
47
+ void p.catch(() => void 0).finally(() => {
48
+ if (pdfInflight.get(fileId) === p) pdfInflight.delete(fileId);
49
+ });
50
+ }
51
+ return p;
52
+ }
53
+ function peekOfficePdf(fileId) {
54
+ return pdfRefCache.get(fileId) ?? null;
55
+ }
56
+ function invalidateOfficeExtraction(fileId) {
57
+ extractionCache.delete(fileId);
58
+ pdfRefCache.delete(fileId);
59
+ }
60
+ export {
61
+ getOfficeExtraction,
62
+ getOfficePdf,
63
+ invalidateOfficeExtraction,
64
+ peekOfficeExtraction,
65
+ peekOfficePdf
66
+ };
67
+ //# sourceMappingURL=office-extraction-cache.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../../../src/files/engine/hooks/office-extraction-cache.ts"],"sourcesContent":["/**\n * features/files/hooks/office-extraction-cache.ts\n *\n * Module-level cache of server-side Office extractions (docx/pptx → markdown),\n * keyed by fileId — the OfficePreview analogue of `blob-cache.ts`. Lives\n * OUTSIDE the lazy previewer chunk so the Redux thunks and the realtime\n * middleware can invalidate it (new version upload / restore / delete)\n * without importing the react-markdown previewer graph.\n *\n * Payloads are small (markdown text), so a simple insertion-capped Map is\n * enough — no LRU, no byte accounting.\n */\n\nimport {\n convertOfficeToPdf,\n extractOfficeMarkdown,\n type OfficeExtraction,\n type OfficeFileRef,\n} from \"../api/office\";\n\nconst extractionCache = new Map<string, OfficeExtraction>();\nconst CACHE_MAX_ENTRIES = 40;\nconst inflight = new Map<string, Promise<OfficeExtraction>>();\n\n// PDF-derivative refs for the visual (slides/pages) preview. The server lane\n// is idempotent per source revision, so caching the FileRef here only saves\n// the round-trip — a stale entry can't produce a wrong document, and it is\n// dropped through the same invalidation choke points as extractions.\nconst pdfRefCache = new Map<string, OfficeFileRef>();\nconst pdfInflight = new Map<string, Promise<OfficeFileRef>>();\n\n/** Cached-or-fetched extraction, deduplicated across simultaneous mounts. */\nexport function getOfficeExtraction(fileId: string): Promise<OfficeExtraction> {\n const cached = extractionCache.get(fileId);\n if (cached) return Promise.resolve(cached);\n let p = inflight.get(fileId);\n if (!p) {\n p = extractOfficeMarkdown(fileId).then((result) => {\n if (extractionCache.size >= CACHE_MAX_ENTRIES) {\n const oldest = extractionCache.keys().next().value;\n if (oldest !== undefined) extractionCache.delete(oldest);\n }\n extractionCache.set(fileId, result);\n return result;\n });\n inflight.set(fileId, p);\n // Cleanup must not create an unhandled rejection when the fetch fails —\n // subscribers handle `p`'s rejection themselves; this chain swallows it.\n void p\n .catch(() => undefined)\n .finally(() => {\n if (inflight.get(fileId) === p) inflight.delete(fileId);\n });\n }\n return p;\n}\n\n/** Synchronous cache peek — lets the previewer render instantly on remount. */\nexport function peekOfficeExtraction(fileId: string): OfficeExtraction | null {\n return extractionCache.get(fileId) ?? null;\n}\n\n/**\n * Cached-or-created PDF derivative of an Office file (server-side LibreOffice\n * lane, idempotent per source revision) — powers the visual slides/pages\n * preview. Deduplicated across simultaneous mounts.\n */\nexport function getOfficePdf(fileId: string): Promise<OfficeFileRef> {\n const cached = pdfRefCache.get(fileId);\n if (cached) return Promise.resolve(cached);\n let p = pdfInflight.get(fileId);\n if (!p) {\n p = convertOfficeToPdf(fileId).then((result) => {\n if (pdfRefCache.size >= CACHE_MAX_ENTRIES) {\n const oldest = pdfRefCache.keys().next().value;\n if (oldest !== undefined) pdfRefCache.delete(oldest);\n }\n pdfRefCache.set(fileId, result);\n return result;\n });\n pdfInflight.set(fileId, p);\n void p\n .catch(() => undefined)\n .finally(() => {\n if (pdfInflight.get(fileId) === p) pdfInflight.delete(fileId);\n });\n }\n return p;\n}\n\n/** Synchronous PDF-ref peek — lets the visual previewer render instantly. */\nexport function peekOfficePdf(fileId: string): OfficeFileRef | null {\n return pdfRefCache.get(fileId) ?? null;\n}\n\n/** Drop the cached extraction AND pdf ref — call wherever the blob cache is\n * invalidated (new version upload, restore, delete, realtime update). */\nexport function invalidateOfficeExtraction(fileId: string): void {\n extractionCache.delete(fileId);\n pdfRefCache.delete(fileId);\n}\n"],"mappings":"AAaA;AAAA,EACE;AAAA,EACA;AAAA,OAGK;AAEP,MAAM,kBAAkB,oBAAI,IAA8B;AAC1D,MAAM,oBAAoB;AAC1B,MAAM,WAAW,oBAAI,IAAuC;AAM5D,MAAM,cAAc,oBAAI,IAA2B;AACnD,MAAM,cAAc,oBAAI,IAAoC;AAGrD,SAAS,oBAAoB,QAA2C;AAC7E,QAAM,SAAS,gBAAgB,IAAI,MAAM;AACzC,MAAI,OAAQ,QAAO,QAAQ,QAAQ,MAAM;AACzC,MAAI,IAAI,SAAS,IAAI,MAAM;AAC3B,MAAI,CAAC,GAAG;AACN,QAAI,sBAAsB,MAAM,EAAE,KAAK,CAAC,WAAW;AACjD,UAAI,gBAAgB,QAAQ,mBAAmB;AAC7C,cAAM,SAAS,gBAAgB,KAAK,EAAE,KAAK,EAAE;AAC7C,YAAI,WAAW,OAAW,iBAAgB,OAAO,MAAM;AAAA,MACzD;AACA,sBAAgB,IAAI,QAAQ,MAAM;AAClC,aAAO;AAAA,IACT,CAAC;AACD,aAAS,IAAI,QAAQ,CAAC;AAGtB,SAAK,EACF,MAAM,MAAM,MAAS,EACrB,QAAQ,MAAM;AACb,UAAI,SAAS,IAAI,MAAM,MAAM,EAAG,UAAS,OAAO,MAAM;AAAA,IACxD,CAAC;AAAA,EACL;AACA,SAAO;AACT;AAGO,SAAS,qBAAqB,QAAyC;AAC5E,SAAO,gBAAgB,IAAI,MAAM,KAAK;AACxC;AAOO,SAAS,aAAa,QAAwC;AACnE,QAAM,SAAS,YAAY,IAAI,MAAM;AACrC,MAAI,OAAQ,QAAO,QAAQ,QAAQ,MAAM;AACzC,MAAI,IAAI,YAAY,IAAI,MAAM;AAC9B,MAAI,CAAC,GAAG;AACN,QAAI,mBAAmB,MAAM,EAAE,KAAK,CAAC,WAAW;AAC9C,UAAI,YAAY,QAAQ,mBAAmB;AACzC,cAAM,SAAS,YAAY,KAAK,EAAE,KAAK,EAAE;AACzC,YAAI,WAAW,OAAW,aAAY,OAAO,MAAM;AAAA,MACrD;AACA,kBAAY,IAAI,QAAQ,MAAM;AAC9B,aAAO;AAAA,IACT,CAAC;AACD,gBAAY,IAAI,QAAQ,CAAC;AACzB,SAAK,EACF,MAAM,MAAM,MAAS,EACrB,QAAQ,MAAM;AACb,UAAI,YAAY,IAAI,MAAM,MAAM,EAAG,aAAY,OAAO,MAAM;AAAA,IAC9D,CAAC;AAAA,EACL;AACA,SAAO;AACT;AAGO,SAAS,cAAc,QAAsC;AAClE,SAAO,YAAY,IAAI,MAAM,KAAK;AACpC;AAIO,SAAS,2BAA2B,QAAsB;AAC/D,kBAAgB,OAAO,MAAM;AAC7B,cAAY,OAAO,MAAM;AAC3B;","names":[]}
@@ -0,0 +1,166 @@
1
+ /**
2
+ * The files engine's host port — the ONE place a client hands the engine what
3
+ * only it can own: its database client, its AI Matrx server client, its Redux
4
+ * store, its share-link calls, its organization chooser and its notifications.
5
+ *
6
+ * Everything else the engine needs (the slice, thunks, middlewares, uploads,
7
+ * the resolver, the typed REST verbs, session retry, claims) is package code.
8
+ *
9
+ * Configure once, before the store is created:
10
+ *
11
+ * configureFilesHost({ db, server, store: () => store, ... });
12
+ *
13
+ * Every seam module reads the host at CALL time, never at import time, so the
14
+ * order of module loading never matters. Used before configuration, a call
15
+ * throws `FilesHostNotConfiguredError`, which names the remedy.
16
+ */
17
+ import type { SupabaseClient } from "@supabase/supabase-js";
18
+ export interface FilesRequestOptions {
19
+ requestId?: string;
20
+ signal?: AbortSignal;
21
+ baseUrlOverride?: string;
22
+ timeoutMs?: number;
23
+ captureErrors?: boolean;
24
+ guestFingerprint?: string;
25
+ idempotencyKey?: string;
26
+ organizationId?: string;
27
+ bodyCarriedRead?: boolean;
28
+ cloudFilesBypass?: string;
29
+ }
30
+ export interface FilesResponseMeta {
31
+ requestId: string;
32
+ status: number;
33
+ serverRequestId: string | null;
34
+ }
35
+ export interface FilesUploadProgressEvent {
36
+ loaded: number;
37
+ total: number;
38
+ }
39
+ export interface FilesDownloadProgressEvent {
40
+ loaded: number;
41
+ total: number | null;
42
+ }
43
+ type Envelope<T> = Promise<{
44
+ data: T;
45
+ meta: FilesResponseMeta;
46
+ }>;
47
+ type BlobEnvelope = Promise<{
48
+ blob: Blob;
49
+ meta: FilesResponseMeta;
50
+ filename: string | null;
51
+ }>;
52
+ /** The host's AI Matrx server client (matrx-frontend: `lib/python-client`). */
53
+ export interface FilesServerClient {
54
+ resolveFilesBaseUrl(override?: string): string;
55
+ resolveBaseUrlForPath(path: string, override?: string, method?: string): string;
56
+ buildHeaders(opts: FilesRequestOptions, includeContentType: boolean, method?: string): Promise<{
57
+ headers: Record<string, string>;
58
+ requestId: string;
59
+ }>;
60
+ getJson<T>(path: string, opts?: FilesRequestOptions): Envelope<T>;
61
+ postJson<T, B = unknown>(path: string, body: B, opts?: FilesRequestOptions): Envelope<T>;
62
+ patchJson<T, B = unknown>(path: string, body: B, opts?: FilesRequestOptions): Envelope<T>;
63
+ putJson<T, B = unknown>(path: string, body: B, opts?: FilesRequestOptions): Envelope<T>;
64
+ del<T = null>(path: string, opts?: FilesRequestOptions): Promise<{
65
+ data: T | null;
66
+ meta: FilesResponseMeta;
67
+ }>;
68
+ delJson<T = null, B = unknown>(path: string, body: B, opts?: FilesRequestOptions): Promise<{
69
+ data: T | null;
70
+ meta: FilesResponseMeta;
71
+ }>;
72
+ postMultipart<T>(path: string, form: FormData, opts?: FilesRequestOptions): Envelope<T>;
73
+ uploadWithProgress<T>(path: string, form: FormData, onProgress: (event: FilesUploadProgressEvent) => void, opts?: FilesRequestOptions): Envelope<T>;
74
+ downloadBlob(path: string, opts?: FilesRequestOptions): BlobEnvelope;
75
+ downloadBlobWithProgress(path: string, onProgress: (event: FilesDownloadProgressEvent) => void, opts?: FilesRequestOptions): BlobEnvelope;
76
+ }
77
+ export interface FilesShareLink {
78
+ id: string;
79
+ token: string;
80
+ shortToken: string | null;
81
+ permissionLevel: string;
82
+ label: string | null;
83
+ expiresAt: string | null;
84
+ maxUses: number | null;
85
+ useCount: number;
86
+ isActive: boolean;
87
+ createdAt: string | null;
88
+ lastUsedAt: string | null;
89
+ }
90
+ export interface FilesCreateShareLinkOptions {
91
+ resourceType: string;
92
+ resourceId: string;
93
+ permissionLevel?: string;
94
+ expiresAt?: string | null;
95
+ maxUses?: number | null;
96
+ label?: string | null;
97
+ }
98
+ /** The host's share-link store (matrx-frontend: `utils/permissions/shareLinks`). */
99
+ export interface FilesShareLinks {
100
+ shareLinkUrl(token: string): string;
101
+ createShareLink(options: FilesCreateShareLinkOptions): Promise<{
102
+ success: boolean;
103
+ token?: string;
104
+ shortToken?: string | null;
105
+ url?: string;
106
+ error?: string;
107
+ }>;
108
+ listShareLinks(resourceType: string, resourceId: string): Promise<FilesShareLink[]>;
109
+ revokeShareLink(linkId: string): Promise<{
110
+ success: boolean;
111
+ error?: string;
112
+ }>;
113
+ }
114
+ /** The store the engine's slice is mounted in (under `cloudFiles`). */
115
+ export interface FilesStore {
116
+ getState(): unknown;
117
+ dispatch(action: unknown): unknown;
118
+ }
119
+ /** Where new uploads land by default: the person's current organization / project / task. */
120
+ export interface FilesScope {
121
+ /** The signed-in person (ownership checks). */
122
+ userId?: string | null;
123
+ organizationId: string | null;
124
+ projectId: string | null;
125
+ taskId: string | null;
126
+ }
127
+ export interface FilesNotifyPort {
128
+ success(message: string, options?: {
129
+ description?: string;
130
+ id?: string | number;
131
+ duration?: number;
132
+ }): void;
133
+ error(message: string, options?: {
134
+ description?: string;
135
+ id?: string | number;
136
+ duration?: number;
137
+ }): void;
138
+ }
139
+ export interface FilesHost {
140
+ /** A supabase-js client for the signed-in person (the engine reads the `files` and `iam` schemas). */
141
+ db: SupabaseClient<any, any, any>;
142
+ server: FilesServerClient;
143
+ /** The store the `cloudFiles` reducer is mounted in; null until it exists. */
144
+ store(): FilesStore | null;
145
+ /** The person's current organization / project / task, read from `state`. Default: none. */
146
+ scope?(state: unknown): FilesScope;
147
+ /**
148
+ * Resolve the organization a write lands in. Given a declared id, return it;
149
+ * otherwise ask the person (the host's chooser). Default: the declared id,
150
+ * else the scope's organization, else a named error.
151
+ */
152
+ ensureOrganizationContext?(options: {
153
+ organizationId?: string | null;
154
+ }): Promise<string>;
155
+ shareLinks?: FilesShareLinks;
156
+ notify?: FilesNotifyPort;
157
+ }
158
+ export declare class FilesHostNotConfiguredError extends Error {
159
+ constructor(member: string);
160
+ }
161
+ export declare function configureFilesHost(next: FilesHost): void;
162
+ export declare function isFilesHostConfigured(): boolean;
163
+ export declare function getFilesHost(member: string): FilesHost;
164
+ /** For tests: drop the configured host. */
165
+ export declare function __resetFilesHostForTests(): void;
166
+ export {};
@@ -0,0 +1,30 @@
1
+ class FilesHostNotConfiguredError extends Error {
2
+ constructor(member) {
3
+ super(
4
+ `@ai-matrx/media/files-engine: "${member}" was used before configureFilesHost(...) ran. Call configureFilesHost({ db, server, store }) once at startup, before the store is created.`
5
+ );
6
+ this.name = "FilesHostNotConfiguredError";
7
+ }
8
+ }
9
+ let host = null;
10
+ function configureFilesHost(next) {
11
+ host = next;
12
+ }
13
+ function isFilesHostConfigured() {
14
+ return host !== null;
15
+ }
16
+ function getFilesHost(member) {
17
+ if (!host) throw new FilesHostNotConfiguredError(member);
18
+ return host;
19
+ }
20
+ function __resetFilesHostForTests() {
21
+ host = null;
22
+ }
23
+ export {
24
+ FilesHostNotConfiguredError,
25
+ __resetFilesHostForTests,
26
+ configureFilesHost,
27
+ getFilesHost,
28
+ isFilesHostConfigured
29
+ };
30
+ //# sourceMappingURL=configure.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../../../src/files/engine/host/configure.ts"],"sourcesContent":["/**\n * The files engine's host port — the ONE place a client hands the engine what\n * only it can own: its database client, its AI Matrx server client, its Redux\n * store, its share-link calls, its organization chooser and its notifications.\n *\n * Everything else the engine needs (the slice, thunks, middlewares, uploads,\n * the resolver, the typed REST verbs, session retry, claims) is package code.\n *\n * Configure once, before the store is created:\n *\n * configureFilesHost({ db, server, store: () => store, ... });\n *\n * Every seam module reads the host at CALL time, never at import time, so the\n * order of module loading never matters. Used before configuration, a call\n * throws `FilesHostNotConfiguredError`, which names the remedy.\n */\n\nimport type { SupabaseClient } from \"@supabase/supabase-js\";\n\nexport interface FilesRequestOptions {\n requestId?: string;\n signal?: AbortSignal;\n baseUrlOverride?: string;\n timeoutMs?: number;\n captureErrors?: boolean;\n guestFingerprint?: string;\n idempotencyKey?: string;\n organizationId?: string;\n bodyCarriedRead?: boolean;\n cloudFilesBypass?: string;\n}\n\nexport interface FilesResponseMeta {\n requestId: string;\n status: number;\n serverRequestId: string | null;\n}\n\nexport interface FilesUploadProgressEvent {\n loaded: number;\n total: number;\n}\n\nexport interface FilesDownloadProgressEvent {\n loaded: number;\n total: number | null;\n}\n\ntype Envelope<T> = Promise<{ data: T; meta: FilesResponseMeta }>;\ntype BlobEnvelope = Promise<{ blob: Blob; meta: FilesResponseMeta; filename: string | null }>;\n\n/** The host's AI Matrx server client (matrx-frontend: `lib/python-client`). */\nexport interface FilesServerClient {\n resolveFilesBaseUrl(override?: string): string;\n resolveBaseUrlForPath(path: string, override?: string, method?: string): string;\n buildHeaders(\n opts: FilesRequestOptions,\n includeContentType: boolean,\n method?: string,\n ): Promise<{ headers: Record<string, string>; requestId: string }>;\n getJson<T>(path: string, opts?: FilesRequestOptions): Envelope<T>;\n postJson<T, B = unknown>(path: string, body: B, opts?: FilesRequestOptions): Envelope<T>;\n patchJson<T, B = unknown>(path: string, body: B, opts?: FilesRequestOptions): Envelope<T>;\n putJson<T, B = unknown>(path: string, body: B, opts?: FilesRequestOptions): Envelope<T>;\n del<T = null>(path: string, opts?: FilesRequestOptions): Promise<{ data: T | null; meta: FilesResponseMeta }>;\n delJson<T = null, B = unknown>(\n path: string,\n body: B,\n opts?: FilesRequestOptions,\n ): Promise<{ data: T | null; meta: FilesResponseMeta }>;\n postMultipart<T>(path: string, form: FormData, opts?: FilesRequestOptions): Envelope<T>;\n uploadWithProgress<T>(\n path: string,\n form: FormData,\n onProgress: (event: FilesUploadProgressEvent) => void,\n opts?: FilesRequestOptions,\n ): Envelope<T>;\n downloadBlob(path: string, opts?: FilesRequestOptions): BlobEnvelope;\n downloadBlobWithProgress(\n path: string,\n onProgress: (event: FilesDownloadProgressEvent) => void,\n opts?: FilesRequestOptions,\n ): BlobEnvelope;\n}\n\nexport interface FilesShareLink {\n id: string;\n token: string;\n shortToken: string | null;\n permissionLevel: string;\n label: string | null;\n expiresAt: string | null;\n maxUses: number | null;\n useCount: number;\n isActive: boolean;\n createdAt: string | null;\n lastUsedAt: string | null;\n}\n\nexport interface FilesCreateShareLinkOptions {\n resourceType: string;\n resourceId: string;\n permissionLevel?: string;\n expiresAt?: string | null;\n maxUses?: number | null;\n label?: string | null;\n}\n\n/** The host's share-link store (matrx-frontend: `utils/permissions/shareLinks`). */\nexport interface FilesShareLinks {\n shareLinkUrl(token: string): string;\n createShareLink(options: FilesCreateShareLinkOptions): Promise<{\n success: boolean;\n token?: string;\n shortToken?: string | null;\n url?: string;\n error?: string;\n }>;\n listShareLinks(resourceType: string, resourceId: string): Promise<FilesShareLink[]>;\n revokeShareLink(linkId: string): Promise<{ success: boolean; error?: string }>;\n}\n\n/** The store the engine's slice is mounted in (under `cloudFiles`). */\nexport interface FilesStore {\n getState(): unknown;\n dispatch(action: unknown): unknown;\n}\n\n/** Where new uploads land by default: the person's current organization / project / task. */\nexport interface FilesScope {\n /** The signed-in person (ownership checks). */\n userId?: string | null;\n organizationId: string | null;\n projectId: string | null;\n taskId: string | null;\n}\n\nexport interface FilesNotifyPort {\n success(message: string, options?: { description?: string; id?: string | number; duration?: number }): void;\n error(message: string, options?: { description?: string; id?: string | number; duration?: number }): void;\n}\n\nexport interface FilesHost {\n /** A supabase-js client for the signed-in person (the engine reads the `files` and `iam` schemas). */\n db: SupabaseClient<any, any, any>;\n server: FilesServerClient;\n /** The store the `cloudFiles` reducer is mounted in; null until it exists. */\n store(): FilesStore | null;\n /** The person's current organization / project / task, read from `state`. Default: none. */\n scope?(state: unknown): FilesScope;\n /**\n * Resolve the organization a write lands in. Given a declared id, return it;\n * otherwise ask the person (the host's chooser). Default: the declared id,\n * else the scope's organization, else a named error.\n */\n ensureOrganizationContext?(options: { organizationId?: string | null }): Promise<string>;\n shareLinks?: FilesShareLinks;\n notify?: FilesNotifyPort;\n}\n\nexport class FilesHostNotConfiguredError extends Error {\n constructor(member: string) {\n super(\n `@ai-matrx/media/files-engine: \"${member}\" was used before configureFilesHost(...) ran. ` +\n \"Call configureFilesHost({ db, server, store }) once at startup, before the store is created.\",\n );\n this.name = \"FilesHostNotConfiguredError\";\n }\n}\n\nlet host: FilesHost | null = null;\n\nexport function configureFilesHost(next: FilesHost): void {\n host = next;\n}\n\nexport function isFilesHostConfigured(): boolean {\n return host !== null;\n}\n\nexport function getFilesHost(member: string): FilesHost {\n if (!host) throw new FilesHostNotConfiguredError(member);\n return host;\n}\n\n/** For tests: drop the configured host. */\nexport function __resetFilesHostForTests(): void {\n host = null;\n}\n"],"mappings":"AAgKO,MAAM,oCAAoC,MAAM;AAAA,EACrD,YAAY,QAAgB;AAC1B;AAAA,MACE,kCAAkC,MAAM;AAAA,IAE1C;AACA,SAAK,OAAO;AAAA,EACd;AACF;AAEA,IAAI,OAAyB;AAEtB,SAAS,mBAAmB,MAAuB;AACxD,SAAO;AACT;AAEO,SAAS,wBAAiC;AAC/C,SAAO,SAAS;AAClB;AAEO,SAAS,aAAa,QAA2B;AACtD,MAAI,CAAC,KAAM,OAAM,IAAI,4BAA4B,MAAM;AACvD,SAAO;AACT;AAGO,SAAS,2BAAiC;AAC/C,SAAO;AACT;","names":[]}
@@ -0,0 +1,8 @@
1
+ /**
2
+ * Organization seam — where a write lands. The host's chooser asks the person
3
+ * when nothing declared one; a host without a chooser uses the declared id,
4
+ * then the person's current organization, and otherwise refuses by name.
5
+ */
6
+ export declare function ensureOrganizationContext(options?: {
7
+ organizationId?: string | null;
8
+ }): Promise<string>;
@@ -0,0 +1,16 @@
1
+ import { getFilesHost } from "./configure.js";
2
+ import { selectOrganizationId, getStoreSingleton } from "./store.js";
3
+ async function ensureOrganizationContext(options = {}) {
4
+ const host = getFilesHost("ensureOrganizationContext");
5
+ if (host.ensureOrganizationContext) return host.ensureOrganizationContext(options);
6
+ if (options.organizationId) return options.organizationId;
7
+ const current = selectOrganizationId(getStoreSingleton()?.getState());
8
+ if (current) return current;
9
+ throw new Error(
10
+ "@ai-matrx/media/files-engine: this write needs an organization and none is selected \u2014 choose one, then retry."
11
+ );
12
+ }
13
+ export {
14
+ ensureOrganizationContext
15
+ };
16
+ //# sourceMappingURL=org.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../../../src/files/engine/host/org.ts"],"sourcesContent":["/**\n * Organization seam — where a write lands. The host's chooser asks the person\n * when nothing declared one; a host without a chooser uses the declared id,\n * then the person's current organization, and otherwise refuses by name.\n */\n\nimport { getFilesHost } from \"./configure\";\nimport { selectOrganizationId, getStoreSingleton } from \"./store\";\n\nexport async function ensureOrganizationContext(\n options: { organizationId?: string | null } = {},\n): Promise<string> {\n const host = getFilesHost(\"ensureOrganizationContext\");\n if (host.ensureOrganizationContext) return host.ensureOrganizationContext(options);\n if (options.organizationId) return options.organizationId;\n const current = selectOrganizationId(getStoreSingleton()?.getState());\n if (current) return current;\n throw new Error(\n \"@ai-matrx/media/files-engine: this write needs an organization and none is selected — choose one, then retry.\",\n );\n}\n"],"mappings":"AAMA,SAAS,oBAAoB;AAC7B,SAAS,sBAAsB,yBAAyB;AAExD,eAAsB,0BACpB,UAA8C,CAAC,GAC9B;AACjB,QAAM,OAAO,aAAa,2BAA2B;AACrD,MAAI,KAAK,0BAA2B,QAAO,KAAK,0BAA0B,OAAO;AACjF,MAAI,QAAQ,eAAgB,QAAO,QAAQ;AAC3C,QAAM,UAAU,qBAAqB,kBAAkB,GAAG,SAAS,CAAC;AACpE,MAAI,QAAS,QAAO;AACpB,QAAM,IAAI;AAAA,IACR;AAAA,EACF;AACF;","names":[]}
@@ -0,0 +1,60 @@
1
+ /**
2
+ * Server seam — the AI Matrx server client the engine calls (same names the
3
+ * call sites imported from matrx-frontend's `lib/python-client`). Each export
4
+ * forwards at CALL time to the configured host's `server` (`./configure.ts`).
5
+ */
6
+ import { type FilesDownloadProgressEvent, type FilesRequestOptions, type FilesResponseMeta, type FilesUploadProgressEvent } from "./configure.js";
7
+ export type RequestOptions = FilesRequestOptions;
8
+ export type ResponseMeta = FilesResponseMeta;
9
+ export type UploadProgressEvent = FilesUploadProgressEvent;
10
+ export type DownloadProgressEvent = FilesDownloadProgressEvent;
11
+ export declare const resolveFilesBaseUrl: (override?: string) => string;
12
+ export declare const resolveBaseUrlForPath: (path: string, override?: string, method?: string) => string;
13
+ export declare const buildHeaders: (opts: FilesRequestOptions, includeContentType: boolean, method?: string) => Promise<{
14
+ headers: Record<string, string>;
15
+ requestId: string;
16
+ }>;
17
+ export declare const getJson: <T>(path: string, opts?: FilesRequestOptions) => Promise<{
18
+ data: T;
19
+ meta: FilesResponseMeta;
20
+ }>;
21
+ export declare const postJson: <T, B = unknown>(path: string, body: B, opts?: FilesRequestOptions) => Promise<{
22
+ data: T;
23
+ meta: FilesResponseMeta;
24
+ }>;
25
+ export declare const patchJson: <T, B = unknown>(path: string, body: B, opts?: FilesRequestOptions) => Promise<{
26
+ data: T;
27
+ meta: FilesResponseMeta;
28
+ }>;
29
+ export declare const putJson: <T, B = unknown>(path: string, body: B, opts?: FilesRequestOptions) => Promise<{
30
+ data: T;
31
+ meta: FilesResponseMeta;
32
+ }>;
33
+ export declare const del: <T = null>(path: string, opts?: FilesRequestOptions) => Promise<{
34
+ data: T | null;
35
+ meta: FilesResponseMeta;
36
+ }>;
37
+ export declare const delJson: <T = null, B = unknown>(path: string, body: B, opts?: FilesRequestOptions) => Promise<{
38
+ data: T | null;
39
+ meta: FilesResponseMeta;
40
+ }>;
41
+ export declare const postMultipart: <T>(path: string, form: FormData, opts?: FilesRequestOptions) => Promise<{
42
+ data: T;
43
+ meta: FilesResponseMeta;
44
+ }>;
45
+ export declare const uploadWithProgress: <T>(path: string, form: FormData, onProgress: (event: FilesUploadProgressEvent) => void, opts?: FilesRequestOptions) => Promise<{
46
+ data: T;
47
+ meta: FilesResponseMeta;
48
+ }>;
49
+ export declare const downloadBlob: (path: string, opts?: FilesRequestOptions) => Promise<{
50
+ blob: Blob;
51
+ meta: FilesResponseMeta;
52
+ filename: string | null;
53
+ }>;
54
+ export declare const downloadBlobWithProgress: (path: string, onProgress: (event: FilesDownloadProgressEvent) => void, opts?: FilesRequestOptions) => Promise<{
55
+ blob: Blob;
56
+ meta: FilesResponseMeta;
57
+ filename: string | null;
58
+ }>;
59
+ /** Client-generated request id (sent as `X-Request-Id`). */
60
+ export declare function newRequestId(): string;
@@ -0,0 +1,46 @@
1
+ import {
2
+ getFilesHost
3
+ } from "./configure.js";
4
+ function forward(member) {
5
+ const forwarded = (...args) => {
6
+ const target = getFilesHost(`server.${member}`).server[member];
7
+ return target(...args);
8
+ };
9
+ return forwarded;
10
+ }
11
+ const resolveFilesBaseUrl = forward("resolveFilesBaseUrl");
12
+ const resolveBaseUrlForPath = forward("resolveBaseUrlForPath");
13
+ const buildHeaders = forward("buildHeaders");
14
+ const getJson = forward("getJson");
15
+ const postJson = forward("postJson");
16
+ const patchJson = forward("patchJson");
17
+ const putJson = forward("putJson");
18
+ const del = forward("del");
19
+ const delJson = forward("delJson");
20
+ const postMultipart = forward("postMultipart");
21
+ const uploadWithProgress = forward("uploadWithProgress");
22
+ const downloadBlob = forward("downloadBlob");
23
+ const downloadBlobWithProgress = forward("downloadBlobWithProgress");
24
+ function newRequestId() {
25
+ if (typeof crypto !== "undefined" && "randomUUID" in crypto) {
26
+ return crypto.randomUUID();
27
+ }
28
+ return `req_${Date.now().toString(36)}_${Math.random().toString(36).slice(2, 10)}`;
29
+ }
30
+ export {
31
+ buildHeaders,
32
+ del,
33
+ delJson,
34
+ downloadBlob,
35
+ downloadBlobWithProgress,
36
+ getJson,
37
+ newRequestId,
38
+ patchJson,
39
+ postJson,
40
+ postMultipart,
41
+ putJson,
42
+ resolveBaseUrlForPath,
43
+ resolveFilesBaseUrl,
44
+ uploadWithProgress
45
+ };
46
+ //# sourceMappingURL=python-client.js.map