feat: 기능 추가 과정중

This commit is contained in:
donghyeon-ka
2026-07-30 15:58:20 +09:00
parent d3ef801fe6
commit 6c52cdb916
648 changed files with 126325 additions and 6680 deletions
@@ -0,0 +1,741 @@
import type {
CapabilityResult,
Cleanup,
TransferProgress,
} from "./contracts.ts";
/**
* Deep opt-in contracts for browser file and origin-storage capabilities.
*
* These contracts intentionally expose no File, Blob, FileSystemHandle,
* IDBDatabase, IDBTransaction, Cache, Request or Response. A selected project
* copies and narrows only the ports that its product actually owns.
*/
declare const byteCountBrand: unique symbol;
declare const localFileRefBrand: unique symbol;
declare const fileVerificationReceiptBrand: unique symbol;
declare const filePolicyKeyBrand: unique symbol;
declare const filePolicyIntentionBrand: unique symbol;
declare const browserManagedCapabilityReceiptBrand: unique symbol;
declare const authorizedDownloadCapabilityBrand: unique symbol;
declare const authorizedDownloadCapabilityReceiptBrand: unique symbol;
declare const objectIdBrand: unique symbol;
declare const objectGenerationBrand: unique symbol;
export type ByteCount = number & {
readonly [byteCountBrand]: "ByteCount";
};
export type LocalFileRef = string & {
readonly [localFileRefBrand]: "LocalFileRef";
};
export type FileVerificationReceipt = string & {
readonly [fileVerificationReceiptBrand]: "FileVerificationReceipt";
};
export type FilePolicyKey = string & {
readonly [filePolicyKeyBrand]: "FilePolicyKey";
};
export type FilePolicyIntention = string & {
readonly [filePolicyIntentionBrand]: "FilePolicyIntention";
};
/**
* Composition-issued capability selector. Implementations must resolve the
* exact registered object identity, not merely an equal string pair.
*/
export type FilePolicyReference = Readonly<{
policyKey: FilePolicyKey;
intention: FilePolicyIntention;
}>;
export type BrowserManagedDownloadCapabilityReceipt = string & {
readonly [browserManagedCapabilityReceiptBrand]:
"BrowserManagedDownloadCapabilityReceipt";
};
export type AuthorizedDownloadCapabilityReceipt = string & {
readonly [authorizedDownloadCapabilityReceiptBrand]:
"AuthorizedDownloadCapabilityReceipt";
};
/**
* GET-only provider-issued handle. The corresponding URL, signed query and
* headers stay inside the selected transfer adapter's in-memory identity
* vault; a feature cannot replace them or supply its own digest.
*/
export type AuthorizedDownloadCapability = Readonly<{
capabilityReceipt: AuthorizedDownloadCapabilityReceipt;
method: "GET";
binding: Readonly<{
kind: "DOWNLOAD";
resourceId: string;
}>;
mediaType: string;
byteLength: ByteCount;
maxBytes: ByteCount;
expectedSha256: string;
expiresAtEpochMs: number;
readonly [authorizedDownloadCapabilityBrand]:
"AuthorizedDownloadCapability";
}>;
export type DurableObjectId = string & {
readonly [objectIdBrand]: "DurableObjectId";
};
export type ObjectGeneration = number & {
readonly [objectGenerationBrand]: "ObjectGeneration";
};
export type PersistableDataClass =
| "PUBLIC"
| "INTERNAL"
| "PERSONAL"
| "CONFIDENTIAL";
export type FileSelectionSource =
| "NATIVE_INPUT"
| "SYSTEM_PICKER"
| "DROP";
export type FileAcceptRule = Readonly<{
mediaType: string;
extensions: ReadonlyArray<string>;
}>;
export type FileSelectionPolicy = Readonly<{
policyId: string;
purpose: string;
classification: PersistableDataClass;
multiple: boolean;
maxCount: number;
maxFileBytes: ByteCount;
maxTotalBytes: ByteCount;
allowEmpty: boolean;
accept: ReadonlyArray<FileAcceptRule>;
}>;
export type FileSelectionLimitReduction = Readonly<{
maxCount?: number;
maxFileBytes?: ByteCount;
maxTotalBytes?: ByteCount;
}>;
export type FileCandidate = Readonly<{
ref: LocalFileRef;
displayName: string;
sizeBytes: ByteCount;
reportedMediaType: string | null;
lastModifiedEpochMs: number | null;
source: FileSelectionSource;
}>;
export type FileSelectionOutcome =
| Readonly<{
kind: "SELECTED";
files: ReadonlyArray<FileCandidate>;
}>
| Readonly<{ kind: "DISMISSED" }>;
export type FilePickerSupport = Readonly<{
nativeInput: true;
systemOpenPicker: boolean;
systemSavePicker: boolean;
}>;
export interface FilePickerPort {
readonly support: FilePickerSupport;
select(input: {
policy: FilePolicyReference;
limits?: FileSelectionLimitReduction;
signal?: AbortSignal;
}): Promise<CapabilityResult<FileSelectionOutcome>>;
release(ref: LocalFileRef): void;
}
export type FileSignatureResult =
| "MATCHED"
| "MISMATCHED"
| "UNKNOWN";
export type FileBytePattern = Readonly<{
offset: ByteCount;
bytes: ReadonlyArray<number>;
mask?: ReadonlyArray<number>;
}>;
export type FileSignatureRule = Readonly<{
mediaType: string;
extensions: ReadonlyArray<string>;
patterns: ReadonlyArray<FileBytePattern>;
}>;
export type FileInspectionPolicy = Readonly<{
policyId: string;
maxInspectionBytes: ByteCount;
acceptedSignatures: ReadonlyArray<FileSignatureRule>;
}>;
export type FileInspection = Readonly<{
byteLength: ByteCount;
reportedMediaType: string | null;
detectedMediaType: string | null;
normalizedExtension: string | null;
signature: FileSignatureResult;
verificationReceipt: FileVerificationReceipt | null;
}>;
export type FileByteSource = Readonly<{
byteLength: ByteCount | null;
stream(
signal: AbortSignal,
): AsyncIterable<CapabilityResult<Uint8Array>>;
}>;
export interface FileContentPort {
inspect(input: {
ref: LocalFileRef;
policy: FilePolicyReference;
maxInspectionBytes?: ByteCount;
signal: AbortSignal;
}): Promise<CapabilityResult<FileInspection>>;
readRange(input: {
ref: LocalFileRef;
offset: ByteCount;
length: ByteCount;
signal: AbortSignal;
}): Promise<CapabilityResult<Uint8Array>>;
openSource(input: {
ref: LocalFileRef;
signal: AbortSignal;
}): Promise<CapabilityResult<FileByteSource>>;
release(ref: LocalFileRef): void;
}
export type PreviewLease = Readonly<{
url: string;
mediaType: string;
release: Cleanup;
}>;
/**
* Presentation-local capability. The implementation owns object URL creation
* and must revoke each lease on replacement, load failure and unmount.
*/
export interface TransientPreviewPort {
create(input: {
ref: LocalFileRef;
verificationReceipt: FileVerificationReceipt;
policy: FilePolicyReference;
maxPreviewBytes?: ByteCount;
signal: AbortSignal;
}): Promise<CapabilityResult<PreviewLease>>;
dispose(): void;
}
/**
* Feature/backend workflow example, not a browser capability dependency.
* Copy it only when the backend owns authorization, resumable-session expiry,
* integrity verification, content inspection and quarantine promotion.
*/
export type ExampleUploadSession = Readonly<{
sessionId: string;
partSizeBytes: ByteCount;
maxConcurrency: number;
expiresAt: string;
checksumAlgorithm: "SHA-256";
}>;
export type ExampleUploadPartReceipt = Readonly<{
partNumber: number;
acceptedBytes: ByteCount;
checksumSha256: string;
}>;
export type ExampleQuarantinedUpload = Readonly<{
resourceId: string;
state: "QUARANTINED";
}>;
export interface ExampleQuarantinedUploadPort {
create(input: {
purpose: string;
byteLength: ByteCount;
detectedMediaType: string;
signal: AbortSignal;
}): Promise<CapabilityResult<ExampleUploadSession>>;
uploadPart(input: {
sessionId: string;
partNumber: number;
offset: ByteCount;
bytes: Uint8Array;
checksumSha256: string;
idempotencyKey: string;
signal: AbortSignal;
}): Promise<CapabilityResult<ExampleUploadPartReceipt>>;
complete(input: {
sessionId: string;
parts: ReadonlyArray<ExampleUploadPartReceipt>;
signal: AbortSignal;
}): Promise<CapabilityResult<ExampleQuarantinedUpload>>;
abort(
sessionId: string,
signal?: AbortSignal,
): Promise<CapabilityResult<void>>;
}
export type DownloadSource =
| Readonly<{
kind: "BROWSER_MANAGED_RESOURCE";
resourceId: string;
capabilityReceipt: BrowserManagedDownloadCapabilityReceipt;
}>
| Readonly<{
kind: "AUTHORIZED_STREAM_RESOURCE";
resourceId: string;
capability: AuthorizedDownloadCapability;
}>
| Readonly<{
kind: "GENERATED";
bytes: FileByteSource;
expectedSha256?: string;
}>;
export type DownloadStrategy =
| "BROWSER_MANAGED"
| "PROMPT_AND_STREAM"
| "BOUNDED_OBJECT_URL";
export type DownloadOutcome =
| Readonly<{
kind: "BROWSER_HANDOFF";
transferId: string;
}>
| Readonly<{
kind: "SAVED";
transferId: string;
bytesWritten: ByteCount;
integrity: "VERIFIED" | "NOT_PROVIDED";
}>
| Readonly<{ kind: "DISMISSED" }>;
export interface DownloadDeliveryPort {
deliver(input: {
policy: FilePolicyReference;
source: DownloadSource;
suggestedFileName: string;
maxTransferBytes?: ByteCount;
maxBufferedBytes?: ByteCount;
signal: AbortSignal;
onProgress(progress: TransferProgress): void;
}): Promise<CapabilityResult<DownloadOutcome>>;
}
export type BrowserManagedDownloadCapability = Readonly<{
capabilityReceipt: BrowserManagedDownloadCapabilityReceipt;
href: string;
resourceId: string;
mediaType: string;
safeExtension: string;
maxBytes: ByteCount;
expectedSha256?: string;
expiresAtEpochMs: number;
}>;
export interface BrowserManagedDownloadCapabilityResolver {
resolve(input: Readonly<{
resourceId: string;
capabilityReceipt: BrowserManagedDownloadCapabilityReceipt;
}>): CapabilityResult<BrowserManagedDownloadCapability>;
}
export type RegisteredPreviewPolicy = Readonly<{
allowedMediaTypes: ReadonlyArray<string>;
maxPreviewBytes: ByteCount;
}>;
export type RegisteredDownloadPolicy = Readonly<{
strategy: DownloadStrategy;
mediaType: string;
safeExtension: string;
maxTransferBytes: ByteCount;
maxBufferedBytes: ByteCount;
integrity: "OPTIONAL" | "REQUIRED";
}>;
/**
* Dataset policy belongs at composition. Feature and presentation callers
* receive only the reference plus optional reductions.
*/
export type BrowserFilePolicyProfile = Readonly<{
reference: FilePolicyReference;
selection?: FileSelectionPolicy;
inspection?: FileInspectionPolicy;
preview?: RegisteredPreviewPolicy;
download?: RegisteredDownloadPolicy;
}>;
export type BrowserPersistencePolicy = Readonly<{
owner: string;
namespace: string;
classification: PersistableDataClass;
authority: "SERVER" | "LOCAL_FIRST" | "RECONSTRUCTABLE";
accountScope: "ORIGIN_SHARED" | "OPAQUE_PARTITION";
retention:
| Readonly<{ kind: "SESSION" }>
| Readonly<{ kind: "TTL"; maxAgeMs: number }>
| Readonly<{ kind: "UNTIL_SYNCED" }>
| Readonly<{ kind: "EXPLICIT_DELETE" }>;
softBudgetBytes: ByteCount;
hardBudgetBytes: ByteCount;
evictionPriority: "RECONSTRUCTABLE" | "SYNCED_COPY" | "USER_AUTHORED";
logoutAction:
| "KEEP_ORIGIN_SHARED"
| "PURGE_PARTITION"
| "EXPORT_THEN_PURGE";
accountDeletionAction: "KEEP_ORIGIN_SHARED" | "PURGE_PARTITION";
pressureAction: "EVICT_RECONSTRUCTABLE" | "RETAIN";
unavailableFallback: "ONLINE_ONLY" | "READ_ONLY" | "EXPORT_REQUIRED";
}>;
export type OfflineStoreStatus =
| Readonly<{
kind: "READY";
persistence: "BEST_EFFORT" | "PERSISTENT";
}>
| Readonly<{
kind: "READ_ONLY";
reason: "FUTURE_SCHEMA" | "QUOTA" | "RECOVERY";
}>
| Readonly<{
kind: "ONLINE_ONLY";
reason: "UNAVAILABLE" | "MIGRATION_FAILED";
}>
| Readonly<{
kind: "UPGRADE_BLOCKED";
targetVersion: number;
}>
| Readonly<{
kind: "CLOSED";
reason: "VERSION_CHANGE" | "FORCED" | "DISPOSED";
}>;
export type OfflineStoreLifecycleEvent =
| Readonly<{ kind: "STATUS_CHANGED"; status: OfflineStoreStatus }>
| Readonly<{
kind: "MIGRATION_PROGRESS";
migrationId: string;
processedCount: number;
remainingEstimate: number | null;
}>;
export type StoredRecord<T> = Readonly<{
id: string;
revision: number;
payloadVersion: number;
createdAtEpochMs: number;
updatedAtEpochMs: number;
expiresAtEpochMs: number | null;
value: T;
}>;
export type RevisionGuard =
| Readonly<{ kind: "ANY" }>
| Readonly<{ kind: "MUST_NOT_EXIST" }>
| Readonly<{ kind: "MATCH"; revision: number }>;
export type StructuredOfflineMutation<T> =
| Readonly<{
kind: "PUT";
id: string;
value: T;
payloadVersion: number;
expiresAtEpochMs: number | null;
revision: RevisionGuard;
}>
| Readonly<{
kind: "DELETE";
id: string;
revision: RevisionGuard;
}>;
export type OfflineCommitReceipt = Readonly<{
commitId: string;
revisions: Readonly<Record<string, number | null>>;
replayed: boolean;
}>;
export type OfflinePage<T> = Readonly<{
records: ReadonlyArray<StoredRecord<T>>;
nextCursor: string | null;
}>;
/**
* Copy this as a feature-specific repository. Do not expose a generic
* transaction callback, object-store name, index name or schema version.
*/
export interface StructuredOfflineStore<T> {
open(input: {
partitionKey: string;
signal?: AbortSignal;
onLifecycle(event: OfflineStoreLifecycleEvent): void;
}): Promise<CapabilityResult<OfflineStoreStatus>>;
read(
id: string,
signal?: AbortSignal,
): Promise<CapabilityResult<StoredRecord<T> | null>>;
page(input: {
cursor?: string;
limit: number;
includeExpired?: boolean;
signal?: AbortSignal;
}): Promise<CapabilityResult<OfflinePage<T>>>;
commit(input: {
idempotencyKey: string;
mutations: ReadonlyArray<StructuredOfflineMutation<T>>;
signal?: AbortSignal;
}): Promise<CapabilityResult<OfflineCommitReceipt>>;
clearPartition(input: {
reason: "LOGOUT" | "ACCOUNT_DELETION" | "USER_REQUEST";
signal?: AbortSignal;
}): Promise<CapabilityResult<void>>;
status(): OfflineStoreStatus;
close(): void;
}
export type MigrationProgress = Readonly<{
migrationId: string;
state: "PENDING" | "RUNNING" | "COMPLETED" | "FAILED";
processedCount: number;
remainingEstimate: number | null;
}>;
export interface OfflineStoreMaintenancePort {
resumeDataMigration(input: {
migrationId: string;
maxRecords: number;
timeBudgetMs: number;
signal?: AbortSignal;
}): Promise<CapabilityResult<MigrationProgress>>;
purgeExpired(input: {
maxRecords: number;
nowEpochMs: number;
signal?: AbortSignal;
}): Promise<CapabilityResult<Readonly<{ purgedCount: number }>>>;
}
export type StorageEstimate = Readonly<{
usageBytes: number | null;
quotaBytes: number | null;
persisted: boolean | null;
pressure: "UNKNOWN" | "NORMAL" | "PRESSURE" | "CRITICAL";
}>;
export interface StorageDurabilityPort {
inspect(signal?: AbortSignal): Promise<CapabilityResult<StorageEstimate>>;
requestPersistence(input: {
reason: "PROTECT_UNSYNCED_USER_DATA";
userInitiated: true;
signal?: AbortSignal;
}): Promise<CapabilityResult<"GRANTED" | "DENIED">>;
}
export type DurableObjectDataClass =
| "RECONSTRUCTABLE"
| "USER_CREATED_PRIVATE";
export type DurableObjectIntegrity = Readonly<{
algorithm: "SHA-256-TREE-V1";
rootDigest: string;
chunkSizeBytes: ByteCount;
}>;
export type DurableObjectDescriptor = Readonly<{
id: DurableObjectId;
generation: ObjectGeneration;
byteLength: ByteCount;
mediaType: string | null;
integrity: DurableObjectIntegrity;
dataClass: DurableObjectDataClass;
retention:
| Readonly<{ kind: "EXPLICIT_DELETE" }>
| Readonly<{ kind: "EXPIRES"; expiresAt: string }>;
}>;
export type DurableObjectRead = Readonly<{
descriptor: DurableObjectDescriptor;
chunks: AsyncIterable<CapabilityResult<Uint8Array>>;
}>;
export interface DurableObjectStorePort {
capabilities(signal?: AbortSignal): Promise<
CapabilityResult<
Readonly<{
backend: "OPFS" | "INDEXEDDB_BLOB" | "NONE";
persistence: "BEST_EFFORT" | "PERSISTENT";
maxObjectBytes: ByteCount;
}>
>
>;
put(input: {
id: DurableObjectId;
expectedGeneration: ObjectGeneration | null;
source: AsyncIterable<CapabilityResult<Uint8Array>>;
declaredByteLength: ByteCount;
mediaType: string | null;
dataClass: DurableObjectDataClass;
retention: DurableObjectDescriptor["retention"];
signal: AbortSignal;
onProgress(progress: TransferProgress): void;
}): Promise<CapabilityResult<DurableObjectDescriptor>>;
open(
id: DurableObjectId,
signal?: AbortSignal,
): Promise<CapabilityResult<DurableObjectRead>>;
remove(input: {
id: DurableObjectId;
expectedGeneration: ObjectGeneration;
signal?: AbortSignal;
}): Promise<CapabilityResult<void>>;
}
export type ObjectStoreRecoverySummary = Readonly<{
resumedCount: number;
purgedCount: number;
quarantinedCount: number;
nextCursor: string | null;
}>;
export interface DurableObjectMaintenancePort {
reconcile(input: {
timeBudgetMs: number;
maxEntries: number;
cursor?: string;
signal?: AbortSignal;
}): Promise<CapabilityResult<ObjectStoreRecoverySummary>>;
}
export type PublicAssetEntry = Readonly<{
url: string;
byteLength: ByteCount;
mediaType: string;
integritySha256: string;
requestCredentials: "OMIT";
dataClass: "PUBLIC";
}>;
export type PublicCacheLookup =
| Readonly<{
kind: "HIT";
releaseId: string;
entry: PublicAssetEntry;
}>
| Readonly<{
kind: "MISS";
reason:
| "NOT_FOUND"
| "EXPIRED"
| "NO_ACTIVE_RELEASE"
| "POLICY_REJECTED";
}>;
export type PublicCacheInspection = Readonly<{
activeReleaseId: string | null;
previousReleaseId: string | null;
candidateReleaseIds: ReadonlyArray<string>;
ownedBytes: number | null;
}>;
/**
* Platform-local Cache Storage policy facade. Raw Request/Response/Cache
* objects remain inside the adapter, and no application repository imports it.
*/
export interface PublicResponseCacheAdmin {
stageRelease(input: {
releaseId: string;
manifestDigest: string;
entries: ReadonlyArray<PublicAssetEntry>;
signal: AbortSignal;
}): Promise<CapabilityResult<void>>;
activateRelease(input: {
releaseId: string;
manifestDigest: string;
expectedPreviousReleaseId: string | null;
signal?: AbortSignal;
}): Promise<CapabilityResult<void>>;
lookup(input: {
url: string;
requestCredentials: "OMIT";
hasAuthorization: false;
signal?: AbortSignal;
}): Promise<CapabilityResult<PublicCacheLookup>>;
inspect(signal?: AbortSignal): Promise<
CapabilityResult<PublicCacheInspection>
>;
rollback(signal?: AbortSignal): Promise<CapabilityResult<void>>;
deleteOwned(input: {
roles: ReadonlyArray<"CANDIDATE" | "PREVIOUS" | "RUNTIME_PUBLIC">;
signal?: AbortSignal;
}): Promise<CapabilityResult<Readonly<{ deletedCount: number }>>>;
}
export type BrowserFileComposition = Readonly<{
picker: FilePickerPort;
content: FileContentPort;
previews: TransientPreviewPort;
downloads: DownloadDeliveryPort;
}>;
export type StructuredOfflineStorageComposition<T> = Readonly<{
store: StructuredOfflineStore<T>;
maintenance: OfflineStoreMaintenancePort;
}>;
export type StorageDurabilityComposition = Readonly<{
durability: StorageDurabilityPort;
}>;
export type DurableObjectStorageComposition = Readonly<{
store: DurableObjectStorePort;
maintenance: DurableObjectMaintenancePort;
}>;
export type PublicResponseCacheComposition = Readonly<{
cache: PublicResponseCacheAdmin;
}>;
/**
* Explicitly separate from BrowserFileComposition: installing file selection,
* preview or download must never imply a resumable backend upload protocol.
*/
export type ExampleBackendUploadComposition = Readonly<{
upload: ExampleQuarantinedUploadPort;
}>;
File diff suppressed because it is too large Load Diff
+32 -2
View File
@@ -11,19 +11,25 @@ export const OPTIONAL_RECIPE_RUNTIME_SENTINEL =
export type CapabilityFailureCode =
| "ABORTED"
| "AUTH_EXPIRED"
| "BLOCKED"
| "CONFLICT"
| "CONSENT_DENIED"
| "CONTRACT_DRIFT"
| "CORRUPT_DATA"
| "DISCONNECTED"
| "EXPIRED_RESOURCE"
| "INTEGRITY_FAILED"
| "INVALID_INPUT"
| "LIMIT_EXCEEDED"
| "MIGRATION_FAILED"
| "NOT_FOUND"
| "NOT_READABLE"
| "PERMISSION_DENIED"
| "POLICY_REJECTED"
| "PROVIDER_UNAVAILABLE"
| "QUOTA_EXCEEDED"
| "STALE_RESULT"
| "STORAGE_EVICTED"
| "UNSUPPORTED";
export type CapabilityFailure = Readonly<{
@@ -85,13 +91,29 @@ export interface ServiceWorkerUpdatePort {
}
export type TransferProgress = Readonly<{
phase?:
| "VALIDATING"
| "PREPARING"
| "TRANSFERRING"
| "VERIFYING"
| "FINALIZING";
transferredBytes: number;
totalBytes: number | null;
}>;
export type TransferByteSource = Readonly<{
byteLength: number | null;
chunks: AsyncIterable<Uint8Array>;
}>;
export interface FileTransferPort {
upload(input: {
file: Readonly<{ name: string; size: number; type: string }>;
file: Readonly<{
name: string;
size: number;
type: string;
content: TransferByteSource;
}>;
signal: AbortSignal;
onProgress(progress: TransferProgress): void;
}): Promise<CapabilityResult<Readonly<{ resourceId: string }>>>;
@@ -99,7 +121,15 @@ export interface FileTransferPort {
resourceId: string;
signal: AbortSignal;
onProgress(progress: TransferProgress): void;
}): Promise<CapabilityResult<Uint8Array>>;
}): Promise<
CapabilityResult<
Readonly<{
fileName: string;
mediaType: string;
content: TransferByteSource;
}>
>
>;
}
export interface GeneratedApiFacade {
+46 -10
View File
@@ -20,7 +20,7 @@ import type {
ServiceWorkerUpdatePort,
VersionedOfflineRepository,
WorkerTaskPort,
} from "./contracts.js";
} from "./contracts.ts";
export function success<T>(value: T): CapabilityResult<T> {
return Object.freeze({ ok: true, value });
@@ -221,11 +221,35 @@ export class FakeFileTransferAdapter implements FileTransferPort {
) {
return failure("LIMIT_EXCEEDED", false, "File size or type is not allowed.");
}
input.onProgress({
transferredBytes: input.file.size,
totalBytes: input.file.size,
});
return success({ resourceId: `fake:${input.file.name}` });
let transferredBytes = 0;
for await (const chunk of input.file.content.chunks) {
const duringTransfer = aborted(input.signal);
if (duringTransfer) return duringTransfer;
transferredBytes += chunk.byteLength;
if (transferredBytes > input.file.size) {
return failure(
"INTEGRITY_FAILED",
false,
"Uploaded bytes exceeded the declared file size.",
);
}
input.onProgress({
phase: "TRANSFERRING",
transferredBytes,
totalBytes: input.file.size,
});
}
if (
transferredBytes !== input.file.size ||
input.file.content.byteLength !== input.file.size
) {
return failure(
"INTEGRITY_FAILED",
false,
"Uploaded bytes did not match the declared file size.",
);
}
return success({ resourceId: "fake:opaque-resource" });
}
async download(input: Parameters<FileTransferPort["download"]>[0]) {
@@ -235,11 +259,23 @@ export class FakeFileTransferAdapter implements FileTransferPort {
return failure("EXPIRED_RESOURCE", true, "The download link expired.");
}
const bytes = new TextEncoder().encode(input.resourceId);
input.onProgress({
transferredBytes: bytes.byteLength,
totalBytes: bytes.byteLength,
const chunks = async function* () {
if (input.signal.aborted) return;
input.onProgress({
phase: "TRANSFERRING" as const,
transferredBytes: bytes.byteLength,
totalBytes: bytes.byteLength,
});
yield bytes.slice();
};
return success({
fileName: "download.bin",
mediaType: "application/octet-stream",
content: {
byteLength: bytes.byteLength,
chunks: chunks(),
},
});
return success(bytes);
}
}
+4 -2
View File
@@ -1,2 +1,4 @@
export * from "./contracts.js";
export * from "./fake-adapters.js";
export * from "./browser-file-storage-contracts.ts";
export * from "./browser-file-storage-fakes.ts";
export * from "./contracts.ts";
export * from "./fake-adapters.ts";