@ai-matrx/data 0.19.2 → 0.21.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/files.d.cts CHANGED
@@ -381,7 +381,14 @@ interface FileUrls {
381
381
  * `?inline=1` matches the exact spelling the backend emits on
382
382
  * `FileRecord.url`, keeping cache keys identical either way.
383
383
  */
384
- declare function fileUrls(filesBaseUrl: string, fileId: string): FileUrls;
384
+ declare function buildFileUrls(filesBaseUrl: string, fileId: string): FileUrls;
385
+ /**
386
+ * @deprecated Renamed {@link buildFileUrls} (architecture step 14): `fileUrls(fileId)`
387
+ * is the ONE caller-facing signature — the host-bound builder in
388
+ * `@ai-matrx/media/files/engine/handler/utils/python-base`. This alias keeps a
389
+ * `latest` media build working for one release and is then removed.
390
+ */
391
+ declare const fileUrls: typeof buildFileUrls;
385
392
  /**
386
393
  * The clean public byte-streaming share endpoint (`GET /share/{token}`,
387
394
  * inline disposition) — the canonical embeddable URL for a minted link.
@@ -563,7 +570,7 @@ declare function createMatrxFilesClient<TSrc extends string = DurableSrc>(config
563
570
  * panels — this module never logs.
564
571
  */
565
572
 
566
- type FileErrorCode = "access_denied" | "not_found" | "deleted" | "share_link_invalid" | "external_fetch_failed" | "upload_failed" | "internal";
573
+ type FileErrorCode = "access_denied" | "not_found" | "deleted" | "share_link_invalid" | "external_fetch_failed" | "cors_blocked" | "mime_unknown" | "upload_failed" | "upload_cancelled" | "quota_exceeded" | "in_flight" | "internal";
567
574
  declare class FileError extends Error {
568
575
  readonly code: FileErrorCode;
569
576
  readonly fileId?: string | undefined;
@@ -615,6 +622,66 @@ declare function fileErrorFromStatus(status: number, fileId?: string): FileError
615
622
  */
616
623
  declare function classifyUnavailable(err: unknown): MediaUnavailableReason;
617
624
 
625
+ /**
626
+ * Upload dedup guard — one in-flight/just-landed claim per (folder, name, size).
627
+ * Origin: @ai-matrx/media files/engine/upload/uploadDedupGuard.ts (architecture step 14).
628
+ *
629
+ * Root cause (`common-docs/projects/acquisition-frontier/own-files/VERIFICATION.md`
630
+ * §12, 2026-09-18): in a 7-file pile, both zero-byte files were uploaded
631
+ * TWICE, 18s apart, the second auto-renamed "(1)" by `uniqueName()` in
632
+ * `features/files/redux/thunks.ts` — right after the dev server had refused
633
+ * connections. A client-side retry of the whole `uploadFiles()` dispatch
634
+ * after a transient failure re-sends a file whose first attempt may already
635
+ * have landed server-side (the client only knows its OWN request failed —
636
+ * a connection reset gives no proof the server never received the bytes).
637
+ *
638
+ * The obvious fix is the backend's OWN `X-Idempotency-Key` mechanism
639
+ * (`lib/python-client.ts` already sends it, `aidream` already stores it in
640
+ * `metadata._idempotency_key`) — but it is not the full answer here: the
641
+ * key is only reused *within* a single `uploadFiles()` dispatch
642
+ * (`newRequestId()` mints a fresh one per worker() call in thunks.ts), and
643
+ * server-side `_replayable_412` in `aidream/aidream/api/routers/files/__init__.py`
644
+ * only replays a 412 (precondition-failed) response under the key — a
645
+ * SUCCESSFUL upload is never deduped by the server on replay. So a second,
646
+ * independent `uploadFiles()` dispatch for the same intended file — the
647
+ * shape a caller-level retry takes — is indistinguishable from a genuinely
648
+ * new upload to both client and server.
649
+ *
650
+ * This guard closes the gap client-side, per (folder, name, size): the
651
+ * FIRST claim for a signature performs the real network request; any
652
+ * SECOND claim for the same signature while that request is still in
653
+ * flight, or shortly after it landed, reuses the first attempt's outcome
654
+ * instead of sending the bytes again. A claim that ends in failure is
655
+ * cleared immediately, so a genuine "it never landed, please retry" case is
656
+ * never blocked — only a request whose outcome is still pending, or already
657
+ * known-good, is deduped.
658
+ */
659
+ interface DedupedUploadOutcome {
660
+ fileId: string;
661
+ filePath: string;
662
+ }
663
+ /** The dedup key: same folder, same original name, same byte count. */
664
+ declare function uploadDedupKey(folderPath: string, fileName: string, fileSize: number): string;
665
+ type UploadClaim = {
666
+ shared: true;
667
+ promise: Promise<DedupedUploadOutcome>;
668
+ } | {
669
+ shared: false;
670
+ /**
671
+ * The caller that owns this claim MUST call `settle` exactly once
672
+ * with the real upload's outcome (or a rejected promise on failure).
673
+ */
674
+ settle: (outcome: Promise<DedupedUploadOutcome>) => void;
675
+ };
676
+ /**
677
+ * Claim a signature before starting an upload. See module doc for the
678
+ * contract: a `shared: true` result means someone else already owns this
679
+ * signature — await its `promise` instead of sending the file again.
680
+ */
681
+ declare function claimUpload(key: string): UploadClaim;
682
+ /** Test-only: clears every claim so suites don't leak state between tests. */
683
+ declare function __resetUploadDedupGuardForTests(): void;
684
+
618
685
  /**
619
686
  * @ai-matrx/data/files — the `mx_files_session` cookie bootstrap.
620
687
  *
@@ -683,4 +750,4 @@ interface FilesSession {
683
750
  }
684
751
  declare function createFilesSession(options: FilesSessionOptions): FilesSession;
685
752
 
686
- export { type BlobCacheEntry, type BlobCachePort, type DurableSrc, ExternalFetchError, FileAccessDeniedError, FileDeletedError, FileError, type FileErrorCode, type FileMetadata, type FileMetadataPort, FileNotFoundError, type FileOrigins, type FileRecordWire, type FileSessionResponseWire, type FileShownTo, FileUploadError, type FileUploadResponseWire, type FileUrlRecognizer, type FileUrls, type FilesBlobHandle, type FilesDiagnosticsEvent, type FilesDiagnosticsSink, type FilesRecoveryReport, type FilesRecoveryVerdict, type FilesResolution, type FilesSession, type FilesSessionOptions, type FilesSessionSnapshot, type MatrxFilesClient, type MatrxFilesClientConfig, type MediaKind, type MediaMultiUploadResult, type MediaRef, type MediaRefLike, type MediaUnavailableReason, type MediaUploadOptions, type MediaUploadResult, type MediaUrlKind, SIGNED_URL_RE, ShareLinkInvalidError, type ShareLinkRecordWire, TUS_TRANSPORT_THRESHOLD_BYTES, type UploadTransport, classifyMediaUrl, classifyUnavailable, createFileUrlRecognizer, createFilesSession, createMatrxFilesClient, createMemoryBlobCache, fileErrorFromStatus, fileNameFromUrl, fileUrls, isDurableMediaUrl, isSignedUrl, mimeFromUrl, resolveUploadTransport, shareUrl, shareableMediaUrl, uploadAccessFields };
753
+ export { type BlobCacheEntry, type BlobCachePort, type DedupedUploadOutcome, type DurableSrc, ExternalFetchError, FileAccessDeniedError, FileDeletedError, FileError, type FileErrorCode, type FileMetadata, type FileMetadataPort, FileNotFoundError, type FileOrigins, type FileRecordWire, type FileSessionResponseWire, type FileShownTo, FileUploadError, type FileUploadResponseWire, type FileUrlRecognizer, type FileUrls, type FilesBlobHandle, type FilesDiagnosticsEvent, type FilesDiagnosticsSink, type FilesRecoveryReport, type FilesRecoveryVerdict, type FilesResolution, type FilesSession, type FilesSessionOptions, type FilesSessionSnapshot, type MatrxFilesClient, type MatrxFilesClientConfig, type MediaKind, type MediaMultiUploadResult, type MediaRef, type MediaRefLike, type MediaUnavailableReason, type MediaUploadOptions, type MediaUploadResult, type MediaUrlKind, SIGNED_URL_RE, ShareLinkInvalidError, type ShareLinkRecordWire, TUS_TRANSPORT_THRESHOLD_BYTES, type UploadClaim, type UploadTransport, __resetUploadDedupGuardForTests, buildFileUrls, claimUpload, classifyMediaUrl, classifyUnavailable, createFileUrlRecognizer, createFilesSession, createMatrxFilesClient, createMemoryBlobCache, fileErrorFromStatus, fileNameFromUrl, fileUrls, isDurableMediaUrl, isSignedUrl, mimeFromUrl, resolveUploadTransport, shareUrl, shareableMediaUrl, uploadAccessFields, uploadDedupKey };
package/dist/files.d.ts CHANGED
@@ -381,7 +381,14 @@ interface FileUrls {
381
381
  * `?inline=1` matches the exact spelling the backend emits on
382
382
  * `FileRecord.url`, keeping cache keys identical either way.
383
383
  */
384
- declare function fileUrls(filesBaseUrl: string, fileId: string): FileUrls;
384
+ declare function buildFileUrls(filesBaseUrl: string, fileId: string): FileUrls;
385
+ /**
386
+ * @deprecated Renamed {@link buildFileUrls} (architecture step 14): `fileUrls(fileId)`
387
+ * is the ONE caller-facing signature — the host-bound builder in
388
+ * `@ai-matrx/media/files/engine/handler/utils/python-base`. This alias keeps a
389
+ * `latest` media build working for one release and is then removed.
390
+ */
391
+ declare const fileUrls: typeof buildFileUrls;
385
392
  /**
386
393
  * The clean public byte-streaming share endpoint (`GET /share/{token}`,
387
394
  * inline disposition) — the canonical embeddable URL for a minted link.
@@ -563,7 +570,7 @@ declare function createMatrxFilesClient<TSrc extends string = DurableSrc>(config
563
570
  * panels — this module never logs.
564
571
  */
565
572
 
566
- type FileErrorCode = "access_denied" | "not_found" | "deleted" | "share_link_invalid" | "external_fetch_failed" | "upload_failed" | "internal";
573
+ type FileErrorCode = "access_denied" | "not_found" | "deleted" | "share_link_invalid" | "external_fetch_failed" | "cors_blocked" | "mime_unknown" | "upload_failed" | "upload_cancelled" | "quota_exceeded" | "in_flight" | "internal";
567
574
  declare class FileError extends Error {
568
575
  readonly code: FileErrorCode;
569
576
  readonly fileId?: string | undefined;
@@ -615,6 +622,66 @@ declare function fileErrorFromStatus(status: number, fileId?: string): FileError
615
622
  */
616
623
  declare function classifyUnavailable(err: unknown): MediaUnavailableReason;
617
624
 
625
+ /**
626
+ * Upload dedup guard — one in-flight/just-landed claim per (folder, name, size).
627
+ * Origin: @ai-matrx/media files/engine/upload/uploadDedupGuard.ts (architecture step 14).
628
+ *
629
+ * Root cause (`common-docs/projects/acquisition-frontier/own-files/VERIFICATION.md`
630
+ * §12, 2026-09-18): in a 7-file pile, both zero-byte files were uploaded
631
+ * TWICE, 18s apart, the second auto-renamed "(1)" by `uniqueName()` in
632
+ * `features/files/redux/thunks.ts` — right after the dev server had refused
633
+ * connections. A client-side retry of the whole `uploadFiles()` dispatch
634
+ * after a transient failure re-sends a file whose first attempt may already
635
+ * have landed server-side (the client only knows its OWN request failed —
636
+ * a connection reset gives no proof the server never received the bytes).
637
+ *
638
+ * The obvious fix is the backend's OWN `X-Idempotency-Key` mechanism
639
+ * (`lib/python-client.ts` already sends it, `aidream` already stores it in
640
+ * `metadata._idempotency_key`) — but it is not the full answer here: the
641
+ * key is only reused *within* a single `uploadFiles()` dispatch
642
+ * (`newRequestId()` mints a fresh one per worker() call in thunks.ts), and
643
+ * server-side `_replayable_412` in `aidream/aidream/api/routers/files/__init__.py`
644
+ * only replays a 412 (precondition-failed) response under the key — a
645
+ * SUCCESSFUL upload is never deduped by the server on replay. So a second,
646
+ * independent `uploadFiles()` dispatch for the same intended file — the
647
+ * shape a caller-level retry takes — is indistinguishable from a genuinely
648
+ * new upload to both client and server.
649
+ *
650
+ * This guard closes the gap client-side, per (folder, name, size): the
651
+ * FIRST claim for a signature performs the real network request; any
652
+ * SECOND claim for the same signature while that request is still in
653
+ * flight, or shortly after it landed, reuses the first attempt's outcome
654
+ * instead of sending the bytes again. A claim that ends in failure is
655
+ * cleared immediately, so a genuine "it never landed, please retry" case is
656
+ * never blocked — only a request whose outcome is still pending, or already
657
+ * known-good, is deduped.
658
+ */
659
+ interface DedupedUploadOutcome {
660
+ fileId: string;
661
+ filePath: string;
662
+ }
663
+ /** The dedup key: same folder, same original name, same byte count. */
664
+ declare function uploadDedupKey(folderPath: string, fileName: string, fileSize: number): string;
665
+ type UploadClaim = {
666
+ shared: true;
667
+ promise: Promise<DedupedUploadOutcome>;
668
+ } | {
669
+ shared: false;
670
+ /**
671
+ * The caller that owns this claim MUST call `settle` exactly once
672
+ * with the real upload's outcome (or a rejected promise on failure).
673
+ */
674
+ settle: (outcome: Promise<DedupedUploadOutcome>) => void;
675
+ };
676
+ /**
677
+ * Claim a signature before starting an upload. See module doc for the
678
+ * contract: a `shared: true` result means someone else already owns this
679
+ * signature — await its `promise` instead of sending the file again.
680
+ */
681
+ declare function claimUpload(key: string): UploadClaim;
682
+ /** Test-only: clears every claim so suites don't leak state between tests. */
683
+ declare function __resetUploadDedupGuardForTests(): void;
684
+
618
685
  /**
619
686
  * @ai-matrx/data/files — the `mx_files_session` cookie bootstrap.
620
687
  *
@@ -683,4 +750,4 @@ interface FilesSession {
683
750
  }
684
751
  declare function createFilesSession(options: FilesSessionOptions): FilesSession;
685
752
 
686
- export { type BlobCacheEntry, type BlobCachePort, type DurableSrc, ExternalFetchError, FileAccessDeniedError, FileDeletedError, FileError, type FileErrorCode, type FileMetadata, type FileMetadataPort, FileNotFoundError, type FileOrigins, type FileRecordWire, type FileSessionResponseWire, type FileShownTo, FileUploadError, type FileUploadResponseWire, type FileUrlRecognizer, type FileUrls, type FilesBlobHandle, type FilesDiagnosticsEvent, type FilesDiagnosticsSink, type FilesRecoveryReport, type FilesRecoveryVerdict, type FilesResolution, type FilesSession, type FilesSessionOptions, type FilesSessionSnapshot, type MatrxFilesClient, type MatrxFilesClientConfig, type MediaKind, type MediaMultiUploadResult, type MediaRef, type MediaRefLike, type MediaUnavailableReason, type MediaUploadOptions, type MediaUploadResult, type MediaUrlKind, SIGNED_URL_RE, ShareLinkInvalidError, type ShareLinkRecordWire, TUS_TRANSPORT_THRESHOLD_BYTES, type UploadTransport, classifyMediaUrl, classifyUnavailable, createFileUrlRecognizer, createFilesSession, createMatrxFilesClient, createMemoryBlobCache, fileErrorFromStatus, fileNameFromUrl, fileUrls, isDurableMediaUrl, isSignedUrl, mimeFromUrl, resolveUploadTransport, shareUrl, shareableMediaUrl, uploadAccessFields };
753
+ export { type BlobCacheEntry, type BlobCachePort, type DedupedUploadOutcome, type DurableSrc, ExternalFetchError, FileAccessDeniedError, FileDeletedError, FileError, type FileErrorCode, type FileMetadata, type FileMetadataPort, FileNotFoundError, type FileOrigins, type FileRecordWire, type FileSessionResponseWire, type FileShownTo, FileUploadError, type FileUploadResponseWire, type FileUrlRecognizer, type FileUrls, type FilesBlobHandle, type FilesDiagnosticsEvent, type FilesDiagnosticsSink, type FilesRecoveryReport, type FilesRecoveryVerdict, type FilesResolution, type FilesSession, type FilesSessionOptions, type FilesSessionSnapshot, type MatrxFilesClient, type MatrxFilesClientConfig, type MediaKind, type MediaMultiUploadResult, type MediaRef, type MediaRefLike, type MediaUnavailableReason, type MediaUploadOptions, type MediaUploadResult, type MediaUrlKind, SIGNED_URL_RE, ShareLinkInvalidError, type ShareLinkRecordWire, TUS_TRANSPORT_THRESHOLD_BYTES, type UploadClaim, type UploadTransport, __resetUploadDedupGuardForTests, buildFileUrls, claimUpload, classifyMediaUrl, classifyUnavailable, createFileUrlRecognizer, createFilesSession, createMatrxFilesClient, createMemoryBlobCache, fileErrorFromStatus, fileNameFromUrl, fileUrls, isDurableMediaUrl, isSignedUrl, mimeFromUrl, resolveUploadTransport, shareUrl, shareableMediaUrl, uploadAccessFields, uploadDedupKey };
package/dist/files.js CHANGED
@@ -197,11 +197,12 @@ function fileNameFromUrl(url) {
197
197
  if (!last || !/\.[a-z0-9]{1,5}$/i.test(last)) return null;
198
198
  return decodeURIComponent(last);
199
199
  }
200
- function fileUrls(filesBaseUrl, fileId) {
200
+ function buildFileUrls(filesBaseUrl, fileId) {
201
201
  const id = encodeURIComponent(fileId);
202
202
  const base = `${filesBaseUrl.replace(/\/$/, "")}/files/${id}/download`;
203
203
  return { download: base, inline: `${base}?inline=1` };
204
204
  }
205
+ var fileUrls = buildFileUrls;
205
206
  function shareUrl(filesBaseUrl, token) {
206
207
  return `${filesBaseUrl.replace(/\/$/, "")}/share/${encodeURIComponent(token)}`;
207
208
  }
@@ -699,7 +700,7 @@ function createMatrxFilesClient(config) {
699
700
  const inflightDownloads = /* @__PURE__ */ new Map();
700
701
  async function fetchFileBytes(fileId) {
701
702
  const headers = await authHeaders();
702
- const response = await fetchImpl(fileUrls(filesBase(), fileId).download, {
703
+ const response = await fetchImpl(buildFileUrls(filesBase(), fileId).download, {
703
704
  headers
704
705
  });
705
706
  if (!response.ok) {
@@ -961,7 +962,7 @@ function createMatrxFilesClient(config) {
961
962
  }
962
963
  const transport = kind === "video" || kind === "audio" ? "element" : "blob";
963
964
  const resolution = {
964
- src: mint(fileUrls(filesBase(), fileId).inline),
965
+ src: mint(buildFileUrls(filesBase(), fileId).inline),
965
966
  kind,
966
967
  transport,
967
968
  recoverable: true
@@ -1027,7 +1028,7 @@ function createMatrxFilesClient(config) {
1027
1028
  mintStatus: session.snapshot().status,
1028
1029
  fetchBytes: async (base, id) => {
1029
1030
  const headers = await authHeaders();
1030
- return fetchImpl(fileUrls(base, id).download, { headers });
1031
+ return fetchImpl(buildFileUrls(base, id).download, { headers });
1031
1032
  },
1032
1033
  cacheBlob: (id, blob) => {
1033
1034
  const already = blobCache.get(id);
@@ -1079,6 +1080,43 @@ function createMatrxFilesClient(config) {
1079
1080
  };
1080
1081
  return client;
1081
1082
  }
1083
+
1084
+ // src/files/dedup.ts
1085
+ var CLAIM_WINDOW_MS = 3 * 60 * 1e3;
1086
+ var claims = /* @__PURE__ */ new Map();
1087
+ function uploadDedupKey(folderPath, fileName, fileSize) {
1088
+ return [folderPath, fileName, String(fileSize)].join("\\0");
1089
+ }
1090
+ function claimUpload(key) {
1091
+ const now = Date.now();
1092
+ const existing = claims.get(key);
1093
+ if (existing && existing.expiresAt > now) {
1094
+ return { shared: true, promise: existing.promise };
1095
+ }
1096
+ let settleFn;
1097
+ const publicPromise = new Promise(
1098
+ (resolve, reject) => {
1099
+ settleFn = (outcome) => {
1100
+ outcome.then(
1101
+ (value) => {
1102
+ resolve(value);
1103
+ },
1104
+ (err) => {
1105
+ claims.delete(key);
1106
+ reject(err instanceof Error ? err : new Error(String(err)));
1107
+ }
1108
+ );
1109
+ };
1110
+ }
1111
+ );
1112
+ publicPromise.catch(() => {
1113
+ });
1114
+ claims.set(key, { promise: publicPromise, expiresAt: now + CLAIM_WINDOW_MS });
1115
+ return { shared: false, settle: settleFn };
1116
+ }
1117
+ function __resetUploadDedupGuardForTests() {
1118
+ claims.clear();
1119
+ }
1082
1120
  export {
1083
1121
  ExternalFetchError,
1084
1122
  FileAccessDeniedError,
@@ -1089,6 +1127,9 @@ export {
1089
1127
  SIGNED_URL_RE,
1090
1128
  ShareLinkInvalidError,
1091
1129
  TUS_TRANSPORT_THRESHOLD_BYTES,
1130
+ __resetUploadDedupGuardForTests,
1131
+ buildFileUrls,
1132
+ claimUpload,
1092
1133
  classifyMediaUrl,
1093
1134
  classifyUnavailable,
1094
1135
  createFileUrlRecognizer,
@@ -1104,6 +1145,7 @@ export {
1104
1145
  resolveUploadTransport,
1105
1146
  shareUrl,
1106
1147
  shareableMediaUrl,
1107
- uploadAccessFields
1148
+ uploadAccessFields,
1149
+ uploadDedupKey
1108
1150
  };
1109
1151
  //# sourceMappingURL=files.js.map