@ai-matrx/data 0.20.0 → 0.22.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +29 -0
- package/dist/files/tus.cjs +245 -0
- package/dist/files/tus.cjs.map +1 -0
- package/dist/files/tus.d.cts +106 -0
- package/dist/files/tus.d.ts +106 -0
- package/dist/files/tus.js +214 -0
- package/dist/files/tus.js.map +1 -0
- package/dist/files.cjs +46 -6
- package/dist/files.cjs.map +1 -1
- package/dist/files.d.cts +62 -2
- package/dist/files.d.ts +62 -2
- package/dist/files.js +46 -6
- package/dist/files.js.map +1 -1
- package/dist/types.cjs +19 -0
- package/dist/types.cjs.map +1 -0
- package/dist/types.d.cts +53855 -0
- package/dist/types.d.ts +53855 -0
- package/dist/types.js +1 -0
- package/dist/types.js.map +1 -0
- package/package.json +24 -1
package/dist/files.d.cts
CHANGED
|
@@ -381,7 +381,7 @@ 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
|
|
384
|
+
declare function buildFileUrls(filesBaseUrl: string, fileId: string): FileUrls;
|
|
385
385
|
/**
|
|
386
386
|
* The clean public byte-streaming share endpoint (`GET /share/{token}`,
|
|
387
387
|
* inline disposition) — the canonical embeddable URL for a minted link.
|
|
@@ -615,6 +615,66 @@ declare function fileErrorFromStatus(status: number, fileId?: string): FileError
|
|
|
615
615
|
*/
|
|
616
616
|
declare function classifyUnavailable(err: unknown): MediaUnavailableReason;
|
|
617
617
|
|
|
618
|
+
/**
|
|
619
|
+
* Upload dedup guard — one in-flight/just-landed claim per (folder, name, size).
|
|
620
|
+
* Origin: @ai-matrx/media files/engine/upload/uploadDedupGuard.ts (architecture step 14).
|
|
621
|
+
*
|
|
622
|
+
* Root cause (`common-docs/projects/acquisition-frontier/own-files/VERIFICATION.md`
|
|
623
|
+
* §12, 2026-09-18): in a 7-file pile, both zero-byte files were uploaded
|
|
624
|
+
* TWICE, 18s apart, the second auto-renamed "(1)" by `uniqueName()` in
|
|
625
|
+
* `features/files/redux/thunks.ts` — right after the dev server had refused
|
|
626
|
+
* connections. A client-side retry of the whole `uploadFiles()` dispatch
|
|
627
|
+
* after a transient failure re-sends a file whose first attempt may already
|
|
628
|
+
* have landed server-side (the client only knows its OWN request failed —
|
|
629
|
+
* a connection reset gives no proof the server never received the bytes).
|
|
630
|
+
*
|
|
631
|
+
* The obvious fix is the backend's OWN `X-Idempotency-Key` mechanism
|
|
632
|
+
* (`lib/python-client.ts` already sends it, `aidream` already stores it in
|
|
633
|
+
* `metadata._idempotency_key`) — but it is not the full answer here: the
|
|
634
|
+
* key is only reused *within* a single `uploadFiles()` dispatch
|
|
635
|
+
* (`newRequestId()` mints a fresh one per worker() call in thunks.ts), and
|
|
636
|
+
* server-side `_replayable_412` in `aidream/aidream/api/routers/files/__init__.py`
|
|
637
|
+
* only replays a 412 (precondition-failed) response under the key — a
|
|
638
|
+
* SUCCESSFUL upload is never deduped by the server on replay. So a second,
|
|
639
|
+
* independent `uploadFiles()` dispatch for the same intended file — the
|
|
640
|
+
* shape a caller-level retry takes — is indistinguishable from a genuinely
|
|
641
|
+
* new upload to both client and server.
|
|
642
|
+
*
|
|
643
|
+
* This guard closes the gap client-side, per (folder, name, size): the
|
|
644
|
+
* FIRST claim for a signature performs the real network request; any
|
|
645
|
+
* SECOND claim for the same signature while that request is still in
|
|
646
|
+
* flight, or shortly after it landed, reuses the first attempt's outcome
|
|
647
|
+
* instead of sending the bytes again. A claim that ends in failure is
|
|
648
|
+
* cleared immediately, so a genuine "it never landed, please retry" case is
|
|
649
|
+
* never blocked — only a request whose outcome is still pending, or already
|
|
650
|
+
* known-good, is deduped.
|
|
651
|
+
*/
|
|
652
|
+
interface DedupedUploadOutcome {
|
|
653
|
+
fileId: string;
|
|
654
|
+
filePath: string;
|
|
655
|
+
}
|
|
656
|
+
/** The dedup key: same folder, same original name, same byte count. */
|
|
657
|
+
declare function uploadDedupKey(folderPath: string, fileName: string, fileSize: number): string;
|
|
658
|
+
type UploadClaim = {
|
|
659
|
+
shared: true;
|
|
660
|
+
promise: Promise<DedupedUploadOutcome>;
|
|
661
|
+
} | {
|
|
662
|
+
shared: false;
|
|
663
|
+
/**
|
|
664
|
+
* The caller that owns this claim MUST call `settle` exactly once
|
|
665
|
+
* with the real upload's outcome (or a rejected promise on failure).
|
|
666
|
+
*/
|
|
667
|
+
settle: (outcome: Promise<DedupedUploadOutcome>) => void;
|
|
668
|
+
};
|
|
669
|
+
/**
|
|
670
|
+
* Claim a signature before starting an upload. See module doc for the
|
|
671
|
+
* contract: a `shared: true` result means someone else already owns this
|
|
672
|
+
* signature — await its `promise` instead of sending the file again.
|
|
673
|
+
*/
|
|
674
|
+
declare function claimUpload(key: string): UploadClaim;
|
|
675
|
+
/** Test-only: clears every claim so suites don't leak state between tests. */
|
|
676
|
+
declare function __resetUploadDedupGuardForTests(): void;
|
|
677
|
+
|
|
618
678
|
/**
|
|
619
679
|
* @ai-matrx/data/files — the `mx_files_session` cookie bootstrap.
|
|
620
680
|
*
|
|
@@ -683,4 +743,4 @@ interface FilesSession {
|
|
|
683
743
|
}
|
|
684
744
|
declare function createFilesSession(options: FilesSessionOptions): FilesSession;
|
|
685
745
|
|
|
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,
|
|
746
|
+
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, isDurableMediaUrl, isSignedUrl, mimeFromUrl, resolveUploadTransport, shareUrl, shareableMediaUrl, uploadAccessFields, uploadDedupKey };
|
package/dist/files.d.ts
CHANGED
|
@@ -381,7 +381,7 @@ 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
|
|
384
|
+
declare function buildFileUrls(filesBaseUrl: string, fileId: string): FileUrls;
|
|
385
385
|
/**
|
|
386
386
|
* The clean public byte-streaming share endpoint (`GET /share/{token}`,
|
|
387
387
|
* inline disposition) — the canonical embeddable URL for a minted link.
|
|
@@ -615,6 +615,66 @@ declare function fileErrorFromStatus(status: number, fileId?: string): FileError
|
|
|
615
615
|
*/
|
|
616
616
|
declare function classifyUnavailable(err: unknown): MediaUnavailableReason;
|
|
617
617
|
|
|
618
|
+
/**
|
|
619
|
+
* Upload dedup guard — one in-flight/just-landed claim per (folder, name, size).
|
|
620
|
+
* Origin: @ai-matrx/media files/engine/upload/uploadDedupGuard.ts (architecture step 14).
|
|
621
|
+
*
|
|
622
|
+
* Root cause (`common-docs/projects/acquisition-frontier/own-files/VERIFICATION.md`
|
|
623
|
+
* §12, 2026-09-18): in a 7-file pile, both zero-byte files were uploaded
|
|
624
|
+
* TWICE, 18s apart, the second auto-renamed "(1)" by `uniqueName()` in
|
|
625
|
+
* `features/files/redux/thunks.ts` — right after the dev server had refused
|
|
626
|
+
* connections. A client-side retry of the whole `uploadFiles()` dispatch
|
|
627
|
+
* after a transient failure re-sends a file whose first attempt may already
|
|
628
|
+
* have landed server-side (the client only knows its OWN request failed —
|
|
629
|
+
* a connection reset gives no proof the server never received the bytes).
|
|
630
|
+
*
|
|
631
|
+
* The obvious fix is the backend's OWN `X-Idempotency-Key` mechanism
|
|
632
|
+
* (`lib/python-client.ts` already sends it, `aidream` already stores it in
|
|
633
|
+
* `metadata._idempotency_key`) — but it is not the full answer here: the
|
|
634
|
+
* key is only reused *within* a single `uploadFiles()` dispatch
|
|
635
|
+
* (`newRequestId()` mints a fresh one per worker() call in thunks.ts), and
|
|
636
|
+
* server-side `_replayable_412` in `aidream/aidream/api/routers/files/__init__.py`
|
|
637
|
+
* only replays a 412 (precondition-failed) response under the key — a
|
|
638
|
+
* SUCCESSFUL upload is never deduped by the server on replay. So a second,
|
|
639
|
+
* independent `uploadFiles()` dispatch for the same intended file — the
|
|
640
|
+
* shape a caller-level retry takes — is indistinguishable from a genuinely
|
|
641
|
+
* new upload to both client and server.
|
|
642
|
+
*
|
|
643
|
+
* This guard closes the gap client-side, per (folder, name, size): the
|
|
644
|
+
* FIRST claim for a signature performs the real network request; any
|
|
645
|
+
* SECOND claim for the same signature while that request is still in
|
|
646
|
+
* flight, or shortly after it landed, reuses the first attempt's outcome
|
|
647
|
+
* instead of sending the bytes again. A claim that ends in failure is
|
|
648
|
+
* cleared immediately, so a genuine "it never landed, please retry" case is
|
|
649
|
+
* never blocked — only a request whose outcome is still pending, or already
|
|
650
|
+
* known-good, is deduped.
|
|
651
|
+
*/
|
|
652
|
+
interface DedupedUploadOutcome {
|
|
653
|
+
fileId: string;
|
|
654
|
+
filePath: string;
|
|
655
|
+
}
|
|
656
|
+
/** The dedup key: same folder, same original name, same byte count. */
|
|
657
|
+
declare function uploadDedupKey(folderPath: string, fileName: string, fileSize: number): string;
|
|
658
|
+
type UploadClaim = {
|
|
659
|
+
shared: true;
|
|
660
|
+
promise: Promise<DedupedUploadOutcome>;
|
|
661
|
+
} | {
|
|
662
|
+
shared: false;
|
|
663
|
+
/**
|
|
664
|
+
* The caller that owns this claim MUST call `settle` exactly once
|
|
665
|
+
* with the real upload's outcome (or a rejected promise on failure).
|
|
666
|
+
*/
|
|
667
|
+
settle: (outcome: Promise<DedupedUploadOutcome>) => void;
|
|
668
|
+
};
|
|
669
|
+
/**
|
|
670
|
+
* Claim a signature before starting an upload. See module doc for the
|
|
671
|
+
* contract: a `shared: true` result means someone else already owns this
|
|
672
|
+
* signature — await its `promise` instead of sending the file again.
|
|
673
|
+
*/
|
|
674
|
+
declare function claimUpload(key: string): UploadClaim;
|
|
675
|
+
/** Test-only: clears every claim so suites don't leak state between tests. */
|
|
676
|
+
declare function __resetUploadDedupGuardForTests(): void;
|
|
677
|
+
|
|
618
678
|
/**
|
|
619
679
|
* @ai-matrx/data/files — the `mx_files_session` cookie bootstrap.
|
|
620
680
|
*
|
|
@@ -683,4 +743,4 @@ interface FilesSession {
|
|
|
683
743
|
}
|
|
684
744
|
declare function createFilesSession(options: FilesSessionOptions): FilesSession;
|
|
685
745
|
|
|
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,
|
|
746
|
+
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, isDurableMediaUrl, isSignedUrl, mimeFromUrl, resolveUploadTransport, shareUrl, shareableMediaUrl, uploadAccessFields, uploadDedupKey };
|
package/dist/files.js
CHANGED
|
@@ -197,7 +197,7 @@ 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
|
|
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` };
|
|
@@ -699,7 +699,7 @@ function createMatrxFilesClient(config) {
|
|
|
699
699
|
const inflightDownloads = /* @__PURE__ */ new Map();
|
|
700
700
|
async function fetchFileBytes(fileId) {
|
|
701
701
|
const headers = await authHeaders();
|
|
702
|
-
const response = await fetchImpl(
|
|
702
|
+
const response = await fetchImpl(buildFileUrls(filesBase(), fileId).download, {
|
|
703
703
|
headers
|
|
704
704
|
});
|
|
705
705
|
if (!response.ok) {
|
|
@@ -961,7 +961,7 @@ function createMatrxFilesClient(config) {
|
|
|
961
961
|
}
|
|
962
962
|
const transport = kind === "video" || kind === "audio" ? "element" : "blob";
|
|
963
963
|
const resolution = {
|
|
964
|
-
src: mint(
|
|
964
|
+
src: mint(buildFileUrls(filesBase(), fileId).inline),
|
|
965
965
|
kind,
|
|
966
966
|
transport,
|
|
967
967
|
recoverable: true
|
|
@@ -1027,7 +1027,7 @@ function createMatrxFilesClient(config) {
|
|
|
1027
1027
|
mintStatus: session.snapshot().status,
|
|
1028
1028
|
fetchBytes: async (base, id) => {
|
|
1029
1029
|
const headers = await authHeaders();
|
|
1030
|
-
return fetchImpl(
|
|
1030
|
+
return fetchImpl(buildFileUrls(base, id).download, { headers });
|
|
1031
1031
|
},
|
|
1032
1032
|
cacheBlob: (id, blob) => {
|
|
1033
1033
|
const already = blobCache.get(id);
|
|
@@ -1079,6 +1079,43 @@ function createMatrxFilesClient(config) {
|
|
|
1079
1079
|
};
|
|
1080
1080
|
return client;
|
|
1081
1081
|
}
|
|
1082
|
+
|
|
1083
|
+
// src/files/dedup.ts
|
|
1084
|
+
var CLAIM_WINDOW_MS = 3 * 60 * 1e3;
|
|
1085
|
+
var claims = /* @__PURE__ */ new Map();
|
|
1086
|
+
function uploadDedupKey(folderPath, fileName, fileSize) {
|
|
1087
|
+
return [folderPath, fileName, String(fileSize)].join("\\0");
|
|
1088
|
+
}
|
|
1089
|
+
function claimUpload(key) {
|
|
1090
|
+
const now = Date.now();
|
|
1091
|
+
const existing = claims.get(key);
|
|
1092
|
+
if (existing && existing.expiresAt > now) {
|
|
1093
|
+
return { shared: true, promise: existing.promise };
|
|
1094
|
+
}
|
|
1095
|
+
let settleFn;
|
|
1096
|
+
const publicPromise = new Promise(
|
|
1097
|
+
(resolve, reject) => {
|
|
1098
|
+
settleFn = (outcome) => {
|
|
1099
|
+
outcome.then(
|
|
1100
|
+
(value) => {
|
|
1101
|
+
resolve(value);
|
|
1102
|
+
},
|
|
1103
|
+
(err) => {
|
|
1104
|
+
claims.delete(key);
|
|
1105
|
+
reject(err instanceof Error ? err : new Error(String(err)));
|
|
1106
|
+
}
|
|
1107
|
+
);
|
|
1108
|
+
};
|
|
1109
|
+
}
|
|
1110
|
+
);
|
|
1111
|
+
publicPromise.catch(() => {
|
|
1112
|
+
});
|
|
1113
|
+
claims.set(key, { promise: publicPromise, expiresAt: now + CLAIM_WINDOW_MS });
|
|
1114
|
+
return { shared: false, settle: settleFn };
|
|
1115
|
+
}
|
|
1116
|
+
function __resetUploadDedupGuardForTests() {
|
|
1117
|
+
claims.clear();
|
|
1118
|
+
}
|
|
1082
1119
|
export {
|
|
1083
1120
|
ExternalFetchError,
|
|
1084
1121
|
FileAccessDeniedError,
|
|
@@ -1089,6 +1126,9 @@ export {
|
|
|
1089
1126
|
SIGNED_URL_RE,
|
|
1090
1127
|
ShareLinkInvalidError,
|
|
1091
1128
|
TUS_TRANSPORT_THRESHOLD_BYTES,
|
|
1129
|
+
__resetUploadDedupGuardForTests,
|
|
1130
|
+
buildFileUrls,
|
|
1131
|
+
claimUpload,
|
|
1092
1132
|
classifyMediaUrl,
|
|
1093
1133
|
classifyUnavailable,
|
|
1094
1134
|
createFileUrlRecognizer,
|
|
@@ -1097,13 +1137,13 @@ export {
|
|
|
1097
1137
|
createMemoryBlobCache,
|
|
1098
1138
|
fileErrorFromStatus,
|
|
1099
1139
|
fileNameFromUrl,
|
|
1100
|
-
fileUrls,
|
|
1101
1140
|
isDurableMediaUrl,
|
|
1102
1141
|
isSignedUrl,
|
|
1103
1142
|
mimeFromUrl,
|
|
1104
1143
|
resolveUploadTransport,
|
|
1105
1144
|
shareUrl,
|
|
1106
1145
|
shareableMediaUrl,
|
|
1107
|
-
uploadAccessFields
|
|
1146
|
+
uploadAccessFields,
|
|
1147
|
+
uploadDedupKey
|
|
1108
1148
|
};
|
|
1109
1149
|
//# sourceMappingURL=files.js.map
|