chore: initialize from frontend template 4dc033c
This commit is contained in:
@@ -0,0 +1,279 @@
|
||||
import type { Result } from "../../result.ts";
|
||||
import type { FileByteSource } from "../browser-file-storage/file.ts";
|
||||
import type {
|
||||
BrowserDataFailure,
|
||||
BrowserDataResult,
|
||||
TransferProgress,
|
||||
} from "../browser-file-storage/shared.ts";
|
||||
|
||||
export const RESUMABLE_UPLOAD_PROTOCOL =
|
||||
"PRESIGNED_MULTIPART_V1" as const;
|
||||
export type ResumableUploadProtocol =
|
||||
typeof RESUMABLE_UPLOAD_PROTOCOL;
|
||||
|
||||
/**
|
||||
* Multipart uploads use a bounded part manifest instead of a whole-file
|
||||
* ArrayBuffer. The digest is SHA-256 over the canonical ordered part metadata
|
||||
* and SHA-256 part digests.
|
||||
*/
|
||||
export type UploadFileFingerprint = Readonly<{
|
||||
algorithm: "SHA-256-PARTS-V1";
|
||||
digestHex: string;
|
||||
byteLength: number;
|
||||
partSizeBytes: number;
|
||||
partCount: number;
|
||||
}>;
|
||||
|
||||
export type UploadPartDescriptor = Readonly<{
|
||||
partNumber: number;
|
||||
offset: number;
|
||||
byteLength: number;
|
||||
checksumSha256: string;
|
||||
}>;
|
||||
|
||||
/**
|
||||
* A non-authorizing server acknowledgement. It must be opaque, contain no PII,
|
||||
* URL or credential, and be accepted only with the exact descriptor binding.
|
||||
*/
|
||||
export type UploadPartReceipt = UploadPartDescriptor &
|
||||
Readonly<{
|
||||
receiptToken: string;
|
||||
}>;
|
||||
|
||||
export interface UploadRangeReader {
|
||||
readonly byteLength: number;
|
||||
readRange(input: Readonly<{
|
||||
offset: number;
|
||||
length: number;
|
||||
signal: AbortSignal;
|
||||
}>): Promise<BrowserDataResult<Uint8Array>>;
|
||||
}
|
||||
|
||||
/**
|
||||
* FILE_BYTE_SOURCE supports existing FileByteSource implementations. It must
|
||||
* be replayable for the fingerprint pass and transfer pass. RANGE_READER is
|
||||
* preferred for concurrent uploads and OPFS/file-vault range adapters.
|
||||
*/
|
||||
export type ResumableUploadSource =
|
||||
| Readonly<{
|
||||
kind: "FILE_BYTE_SOURCE";
|
||||
bytes: FileByteSource;
|
||||
}>
|
||||
| Readonly<{
|
||||
kind: "RANGE_READER";
|
||||
reader: UploadRangeReader;
|
||||
}>;
|
||||
|
||||
export type ResumableUploadRequest = Readonly<{
|
||||
/** Opaque, caller-stable operation key. It must not contain a file name. */
|
||||
uploadKey: string;
|
||||
/** Registry-approved backend purpose identifier, not user-provided text. */
|
||||
purpose: string;
|
||||
mediaType: string;
|
||||
source: ResumableUploadSource;
|
||||
signal: AbortSignal;
|
||||
onProgress?: (progress: TransferProgress) => void;
|
||||
}>;
|
||||
|
||||
export type QuarantinedUpload = Readonly<{
|
||||
state: "QUARANTINED";
|
||||
resourceId: string;
|
||||
byteLength: number;
|
||||
replayed: boolean;
|
||||
}>;
|
||||
|
||||
export type UploadAbortOutcome = Readonly<{
|
||||
state: "ABORTED" | "ORPHANED" | "ALREADY_COMPLETED" | "NOT_FOUND";
|
||||
}>;
|
||||
|
||||
export interface ResumableUploadPort {
|
||||
upload(
|
||||
request: ResumableUploadRequest,
|
||||
): Promise<BrowserDataResult<QuarantinedUpload>>;
|
||||
abort(request: Readonly<{
|
||||
uploadKey: string;
|
||||
signal: AbortSignal;
|
||||
}>): Promise<BrowserDataResult<UploadAbortOutcome>>;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retry-After is transport metadata used only by the runtime. It is bounded
|
||||
* before sleeping and is removed from the application-facing failure.
|
||||
*/
|
||||
export type UploadProviderFailure = BrowserDataFailure &
|
||||
Readonly<{
|
||||
retryAfterMs?: number;
|
||||
}>;
|
||||
|
||||
export type UploadProviderResult<Value> = Result<
|
||||
Value,
|
||||
UploadProviderFailure
|
||||
>;
|
||||
|
||||
export type UploadSession = Readonly<{
|
||||
protocol: ResumableUploadProtocol;
|
||||
sessionId: string;
|
||||
requestBindingSha256: string;
|
||||
fingerprint: UploadFileFingerprint;
|
||||
partSizeBytes: number;
|
||||
partCount: number;
|
||||
maxConcurrency: number;
|
||||
expiresAtEpochMs: number;
|
||||
}>;
|
||||
|
||||
export type ActiveUploadStatus = Readonly<{
|
||||
state: "ACTIVE";
|
||||
session: UploadSession;
|
||||
acceptedParts: readonly UploadPartReceipt[];
|
||||
}>;
|
||||
|
||||
export type UploadSessionStatus =
|
||||
| ActiveUploadStatus
|
||||
| Readonly<{
|
||||
state: "QUARANTINED";
|
||||
session: UploadSession;
|
||||
resourceId: string;
|
||||
}>
|
||||
| Readonly<{
|
||||
state: "ABORTED" | "EXPIRED" | "NOT_FOUND";
|
||||
protocol: ResumableUploadProtocol;
|
||||
sessionId: string;
|
||||
requestBindingSha256: string;
|
||||
}>;
|
||||
|
||||
/**
|
||||
* The capability value is intentionally generic. The presigned-transfer
|
||||
* adapter owns its URL/method/header contract; this port neither duplicates
|
||||
* that type nor permits it to enter a durable checkpoint.
|
||||
*/
|
||||
export type UploadPartCapability<Capability> = Readonly<{
|
||||
capability: Capability;
|
||||
uploadBindingSha256: string;
|
||||
expiresAtEpochMs: number;
|
||||
}>;
|
||||
|
||||
export interface ResumableUploadControlPlane<Capability> {
|
||||
createSession(input: Readonly<{
|
||||
protocol: ResumableUploadProtocol;
|
||||
uploadKey: string;
|
||||
purpose: string;
|
||||
mediaType: string;
|
||||
requestBindingSha256: string;
|
||||
fingerprint: UploadFileFingerprint;
|
||||
requestedPartSizeBytes: number;
|
||||
requestedMaxConcurrency: number;
|
||||
idempotencyKey: string;
|
||||
signal: AbortSignal;
|
||||
}>): Promise<UploadProviderResult<UploadSession>>;
|
||||
|
||||
getStatus(input: Readonly<{
|
||||
protocol: ResumableUploadProtocol;
|
||||
sessionId: string;
|
||||
requestBindingSha256: string;
|
||||
fingerprint: UploadFileFingerprint;
|
||||
signal: AbortSignal;
|
||||
}>): Promise<UploadProviderResult<UploadSessionStatus>>;
|
||||
|
||||
issuePartCapability(input: Readonly<{
|
||||
protocol: ResumableUploadProtocol;
|
||||
sessionId: string;
|
||||
requestBindingSha256: string;
|
||||
uploadBindingSha256: string;
|
||||
fingerprint: UploadFileFingerprint;
|
||||
mediaType: string;
|
||||
part: UploadPartDescriptor;
|
||||
idempotencyKey: string;
|
||||
signal: AbortSignal;
|
||||
}>): Promise<UploadProviderResult<UploadPartCapability<Capability>>>;
|
||||
|
||||
complete(input: Readonly<{
|
||||
protocol: ResumableUploadProtocol;
|
||||
sessionId: string;
|
||||
requestBindingSha256: string;
|
||||
fingerprint: UploadFileFingerprint;
|
||||
orderedParts: readonly UploadPartReceipt[];
|
||||
idempotencyKey: string;
|
||||
signal: AbortSignal;
|
||||
}>): Promise<UploadProviderResult<Readonly<{
|
||||
state: "QUARANTINED";
|
||||
protocol: ResumableUploadProtocol;
|
||||
sessionId: string;
|
||||
requestBindingSha256: string;
|
||||
fingerprint: UploadFileFingerprint;
|
||||
resourceId: string;
|
||||
}>>>;
|
||||
|
||||
abort(input: Readonly<{
|
||||
protocol: ResumableUploadProtocol;
|
||||
sessionId: string;
|
||||
requestBindingSha256: string;
|
||||
idempotencyKey: string;
|
||||
signal: AbortSignal;
|
||||
}>): Promise<UploadProviderResult<Readonly<{
|
||||
state: "ABORTED" | "NOT_FOUND" | "EXPIRED" | "ALREADY_COMPLETED";
|
||||
}>>>;
|
||||
}
|
||||
|
||||
export interface UploadPartExecutor<Capability> {
|
||||
uploadPart(input: Readonly<{
|
||||
protocol: ResumableUploadProtocol;
|
||||
capability: Capability;
|
||||
sessionId: string;
|
||||
requestBindingSha256: string;
|
||||
uploadBindingSha256: string;
|
||||
fingerprint: UploadFileFingerprint;
|
||||
mediaType: string;
|
||||
part: UploadPartDescriptor;
|
||||
bytes: Uint8Array;
|
||||
idempotencyKey: string;
|
||||
signal: AbortSignal;
|
||||
}>): Promise<UploadProviderResult<UploadPartReceipt>>;
|
||||
}
|
||||
|
||||
/**
|
||||
* Durable, non-secret recovery state. Implementations must reject any unknown
|
||||
* property so a signed URL, authorization header or user metadata cannot be
|
||||
* smuggled into persistence.
|
||||
*/
|
||||
export type ResumableUploadCheckpoint = Readonly<{
|
||||
schemaVersion: 1;
|
||||
protocol: ResumableUploadProtocol;
|
||||
revision: number;
|
||||
state: "ACTIVE" | "ABORT_PENDING";
|
||||
uploadKey: string;
|
||||
requestBindingSha256: string;
|
||||
fingerprint: UploadFileFingerprint;
|
||||
sessionId: string;
|
||||
sessionExpiresAtEpochMs: number;
|
||||
sessionMaxConcurrency: number;
|
||||
acceptedParts: readonly UploadPartReceipt[];
|
||||
updatedAtEpochMs: number;
|
||||
}>;
|
||||
|
||||
export interface ResumableUploadCheckpointStore {
|
||||
read(
|
||||
uploadKey: string,
|
||||
signal?: AbortSignal,
|
||||
): Promise<BrowserDataResult<ResumableUploadCheckpoint | null>>;
|
||||
compareAndSwap(input: Readonly<{
|
||||
expectedRevision: number | null;
|
||||
checkpoint: ResumableUploadCheckpoint;
|
||||
signal?: AbortSignal;
|
||||
}>): Promise<BrowserDataResult<ResumableUploadCheckpoint>>;
|
||||
remove(input: Readonly<{
|
||||
uploadKey: string;
|
||||
expectedRevision: number;
|
||||
signal?: AbortSignal;
|
||||
}>): Promise<BrowserDataResult<void>>;
|
||||
close(): void;
|
||||
}
|
||||
|
||||
export interface ResumableUploadCheckpointAdmin {
|
||||
/**
|
||||
* Account/logout lifecycle operation for this already-bound opaque partition.
|
||||
* The adapter closes its connection before deletion and bounds blocked waits.
|
||||
*/
|
||||
deletePartition(
|
||||
signal?: AbortSignal,
|
||||
): Promise<BrowserDataResult<Readonly<{ state: "DELETED" }>>>;
|
||||
}
|
||||
Reference in New Issue
Block a user