chore: initialize from frontend template 4dc033c
This commit is contained in:
@@ -0,0 +1,30 @@
|
||||
export type SessionState =
|
||||
| "authenticated"
|
||||
| "unauthenticated"
|
||||
| "recovery-pending"
|
||||
| "integration-failed";
|
||||
|
||||
export type SessionGateway = Readonly<{
|
||||
getState(): SessionState;
|
||||
subscribe(listener: () => void): () => void;
|
||||
beginSignIn(returnTo?: string): Promise<void>;
|
||||
signOut(): Promise<void>;
|
||||
recover(): Promise<"restored" | "no-session">;
|
||||
}>;
|
||||
|
||||
export type CredentialRequestBinding = Readonly<{
|
||||
origin: string;
|
||||
method: string;
|
||||
operationId: string;
|
||||
}>;
|
||||
|
||||
export type CredentialPatch = Readonly<{
|
||||
headers: Readonly<Record<string, string>>;
|
||||
}>;
|
||||
|
||||
export type CredentialAttacher = Readonly<{
|
||||
credentialPatch(binding: CredentialRequestBinding): Promise<CredentialPatch>;
|
||||
onUnauthenticated(): void;
|
||||
}>;
|
||||
|
||||
export type AuthSessionPort = SessionGateway & CredentialAttacher;
|
||||
@@ -0,0 +1,90 @@
|
||||
import type {
|
||||
BrowserDataResult,
|
||||
ByteSource,
|
||||
} from "./shared.ts";
|
||||
|
||||
export type PublicCacheHeader = readonly [name: string, value: string];
|
||||
|
||||
export type PublicCacheAsset = Readonly<{
|
||||
absoluteUrl: string;
|
||||
expectedByteLength: number;
|
||||
expectedContentType: string;
|
||||
integrity: Readonly<{
|
||||
algorithm: "SHA-256";
|
||||
digestHex: string;
|
||||
}>;
|
||||
requestHeaders?: readonly PublicCacheHeader[];
|
||||
}>;
|
||||
|
||||
export type PublicCacheReleaseManifest = Readonly<{
|
||||
releaseRegistryId: string;
|
||||
manifestDigestHex: string;
|
||||
assets: readonly PublicCacheAsset[];
|
||||
}>;
|
||||
|
||||
export type PublicCacheReleaseSummary = Readonly<{
|
||||
releaseRegistryId: string;
|
||||
entryCount: number;
|
||||
totalBytes: number;
|
||||
stagedAtEpochMs: number;
|
||||
}>;
|
||||
|
||||
export type CachedPublicResponse = Readonly<{
|
||||
status: 200;
|
||||
headers: readonly PublicCacheHeader[];
|
||||
body: ByteSource;
|
||||
integrity: Readonly<{
|
||||
algorithm: "SHA-256";
|
||||
digestHex: string;
|
||||
}>;
|
||||
}>;
|
||||
|
||||
export type PublicCacheInspection = Readonly<{
|
||||
activeReleaseRegistryId: string | null;
|
||||
ownedCacheCount: number;
|
||||
unreadableOwnedCacheCount: number;
|
||||
releaseCandidates: readonly Readonly<{
|
||||
releaseRegistryId: string;
|
||||
verified: boolean;
|
||||
entryCount: number | null;
|
||||
}>[];
|
||||
}>;
|
||||
|
||||
export type PublicCacheCleanupReport = Readonly<{
|
||||
inspectedOwnedCaches: number;
|
||||
deletedOwnedCaches: number;
|
||||
retainedOwnedCaches: number;
|
||||
}>;
|
||||
|
||||
export interface PublicResponseCachePort {
|
||||
matchActiveExact(
|
||||
request: Readonly<{
|
||||
absoluteUrl: string;
|
||||
requestHeaders?: readonly PublicCacheHeader[];
|
||||
signal?: AbortSignal;
|
||||
}>,
|
||||
): Promise<BrowserDataResult<CachedPublicResponse | null>>;
|
||||
}
|
||||
|
||||
export interface PublicResponseCacheAdminPort {
|
||||
stageRelease(
|
||||
manifest: PublicCacheReleaseManifest,
|
||||
options?: Readonly<{ signal?: AbortSignal }>,
|
||||
): Promise<BrowserDataResult<PublicCacheReleaseSummary>>;
|
||||
activateRelease(
|
||||
releaseRegistryId: string,
|
||||
manifestDigestHex: string,
|
||||
options?: Readonly<{ signal?: AbortSignal }>,
|
||||
): Promise<BrowserDataResult<PublicCacheReleaseSummary>>;
|
||||
cleanupOwned(
|
||||
request?: Readonly<{
|
||||
signal?: AbortSignal;
|
||||
}>,
|
||||
): Promise<BrowserDataResult<PublicCacheCleanupReport>>;
|
||||
inspect(): Promise<BrowserDataResult<PublicCacheInspection>>;
|
||||
}
|
||||
|
||||
export type PublicResponseCache = Readonly<{
|
||||
responses: PublicResponseCachePort;
|
||||
admin: PublicResponseCacheAdminPort;
|
||||
}>;
|
||||
@@ -0,0 +1,250 @@
|
||||
import type {
|
||||
BrowserDataResult,
|
||||
TransferProgress,
|
||||
} from "./shared.ts";
|
||||
import type { AuthorizedDownloadCapability } from "../browser-transfer/authorized-download.ts";
|
||||
|
||||
/**
|
||||
* Application-owned browser-file contracts.
|
||||
*
|
||||
* Native File, Blob, FileList, FileSystemHandle, Response and ReadableStream
|
||||
* intentionally do not cross this boundary. A transient object URL may cross
|
||||
* only through the presentation-local PreviewLease below; it must never enter
|
||||
* domain state, persistence, diagnostics or a general application cache.
|
||||
*/
|
||||
|
||||
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;
|
||||
|
||||
export type LocalFileRef = string & {
|
||||
readonly [localFileRefBrand]: "LocalFileRef";
|
||||
};
|
||||
|
||||
export type FileVerificationReceipt = string & {
|
||||
readonly [fileVerificationReceiptBrand]: "FileVerificationReceipt";
|
||||
};
|
||||
|
||||
/**
|
||||
* Registry-issued, non-semantic identifiers. A feature receives a frozen
|
||||
* reference from its composition root; presentation must not construct policy
|
||||
* definitions or select another feature's registered policy.
|
||||
*/
|
||||
export type FilePolicyKey = string & {
|
||||
readonly [filePolicyKeyBrand]: "FilePolicyKey";
|
||||
};
|
||||
|
||||
export type FilePolicyIntention = string & {
|
||||
readonly [filePolicyIntentionBrand]: "FilePolicyIntention";
|
||||
};
|
||||
|
||||
export type FilePolicyReference = Readonly<{
|
||||
policyKey: FilePolicyKey;
|
||||
intention: FilePolicyIntention;
|
||||
}>;
|
||||
|
||||
export type FileSelectionSource =
|
||||
| "NATIVE_INPUT"
|
||||
| "SYSTEM_PICKER"
|
||||
| "DROP";
|
||||
|
||||
export type FileSelectionLimitReduction = Readonly<{
|
||||
maxCount?: number;
|
||||
maxFileBytes?: number;
|
||||
maxTotalBytes?: number;
|
||||
}>;
|
||||
|
||||
export type FileCandidate = Readonly<{
|
||||
ref: LocalFileRef;
|
||||
/**
|
||||
* Untrusted, potentially personal display metadata. It must never be used as
|
||||
* a resource identifier or diagnostics attribute.
|
||||
*/
|
||||
displayName: string;
|
||||
sizeBytes: number;
|
||||
reportedMediaType: string | null;
|
||||
lastModifiedEpochMs: number | null;
|
||||
source: FileSelectionSource;
|
||||
}>;
|
||||
|
||||
export type FileSelectionOutcome =
|
||||
| Readonly<{ kind: "SELECTED"; files: readonly FileCandidate[] }>
|
||||
| Readonly<{ kind: "DISMISSED" }>;
|
||||
|
||||
export type FilePickerSupport = Readonly<{
|
||||
nativeInput: true;
|
||||
systemOpenPicker: boolean;
|
||||
systemSavePicker: boolean;
|
||||
}>;
|
||||
|
||||
export interface FilePickerPort {
|
||||
readonly support: FilePickerSupport;
|
||||
|
||||
/**
|
||||
* Must be invoked as the first browser action in a trusted user activation.
|
||||
* A dismissed picker is a successful DISMISSED outcome, not an error.
|
||||
*/
|
||||
select(input: {
|
||||
policy: FilePolicyReference;
|
||||
limits?: FileSelectionLimitReduction;
|
||||
signal?: AbortSignal;
|
||||
}): Promise<BrowserDataResult<FileSelectionOutcome>>;
|
||||
|
||||
release(ref: LocalFileRef): void;
|
||||
}
|
||||
|
||||
export type FileSignatureResult =
|
||||
| "MATCHED"
|
||||
| "MISMATCHED"
|
||||
| "UNKNOWN";
|
||||
|
||||
export type FileInspection = Readonly<{
|
||||
byteLength: number;
|
||||
reportedMediaType: string | null;
|
||||
detectedMediaType: string | null;
|
||||
normalizedExtension: string | null;
|
||||
signature: FileSignatureResult;
|
||||
/**
|
||||
* Issued only for a matched signature and bound inside the transient vault
|
||||
* to this file snapshot and inspection policy.
|
||||
*/
|
||||
verificationReceipt: FileVerificationReceipt | null;
|
||||
}>;
|
||||
|
||||
/**
|
||||
* File-capability byte stream with a closed failure channel. Implementations
|
||||
* must convert native exceptions to BrowserDataResult and never throw a raw
|
||||
* DOMException across the application boundary.
|
||||
*/
|
||||
export interface FileByteSource {
|
||||
readonly byteLength: number | null;
|
||||
stream(
|
||||
signal: AbortSignal,
|
||||
): AsyncIterable<BrowserDataResult<Uint8Array>>;
|
||||
}
|
||||
|
||||
export interface FileContentPort {
|
||||
inspect(input: {
|
||||
ref: LocalFileRef;
|
||||
policy: FilePolicyReference;
|
||||
maxInspectionBytes?: number;
|
||||
signal: AbortSignal;
|
||||
}): Promise<BrowserDataResult<FileInspection>>;
|
||||
|
||||
readRange(input: {
|
||||
ref: LocalFileRef;
|
||||
offset: number;
|
||||
length: number;
|
||||
signal: AbortSignal;
|
||||
}): Promise<BrowserDataResult<Uint8Array>>;
|
||||
|
||||
openSource(input: {
|
||||
ref: LocalFileRef;
|
||||
signal: AbortSignal;
|
||||
}): Promise<BrowserDataResult<FileByteSource>>;
|
||||
|
||||
release(ref: LocalFileRef): void;
|
||||
}
|
||||
|
||||
export type PreviewLease = Readonly<{
|
||||
url: string;
|
||||
mediaType: string;
|
||||
release(): void;
|
||||
}>;
|
||||
|
||||
export interface TransientPreviewPort {
|
||||
create(input: {
|
||||
ref: LocalFileRef;
|
||||
verificationReceipt: FileVerificationReceipt;
|
||||
policy: FilePolicyReference;
|
||||
maxPreviewBytes?: number;
|
||||
signal: AbortSignal;
|
||||
}): Promise<BrowserDataResult<PreviewLease>>;
|
||||
|
||||
dispose(): void;
|
||||
}
|
||||
|
||||
export type BrowserManagedDownloadCapabilityReceipt = string & {
|
||||
readonly [browserManagedCapabilityReceiptBrand]:
|
||||
"BrowserManagedDownloadCapabilityReceipt";
|
||||
};
|
||||
|
||||
export type DownloadSource =
|
||||
| Readonly<{
|
||||
kind: "BROWSER_MANAGED_RESOURCE";
|
||||
resourceId: string;
|
||||
capabilityReceipt: BrowserManagedDownloadCapabilityReceipt;
|
||||
}>
|
||||
| Readonly<{
|
||||
kind: "AUTHORIZED_STREAM_RESOURCE";
|
||||
resourceId: string;
|
||||
/**
|
||||
* Exact provider-issued handle. Raw href/query/header values are never
|
||||
* caller inputs and an equal-looking fabricated handle must be rejected.
|
||||
*/
|
||||
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: number;
|
||||
integrity: "VERIFIED" | "NOT_PROVIDED";
|
||||
}>
|
||||
| Readonly<{ kind: "DISMISSED" }>;
|
||||
|
||||
export interface DownloadDeliveryPort {
|
||||
deliver(input: {
|
||||
policy: FilePolicyReference;
|
||||
source: DownloadSource;
|
||||
suggestedFileName: string;
|
||||
/**
|
||||
* Optional reductions of the composition-owned policy ceiling. These
|
||||
* values can never raise the registered or absolute runtime limits.
|
||||
*/
|
||||
maxTransferBytes?: number;
|
||||
maxBufferedBytes?: number;
|
||||
signal: AbortSignal;
|
||||
onProgress(progress: TransferProgress): void;
|
||||
}): Promise<BrowserDataResult<DownloadOutcome>>;
|
||||
}
|
||||
|
||||
/**
|
||||
* Synchronously resolved, server-enforced handoff capability. The endpoint
|
||||
* behind href must bind and enforce every field, including expiry and the
|
||||
* optional digest; the browser adapter cannot observe navigation bytes.
|
||||
*/
|
||||
export type BrowserManagedDownloadCapability = Readonly<{
|
||||
capabilityReceipt: BrowserManagedDownloadCapabilityReceipt;
|
||||
href: string;
|
||||
resourceId: string;
|
||||
mediaType: string;
|
||||
safeExtension: string;
|
||||
maxBytes: number;
|
||||
expectedSha256?: string;
|
||||
expiresAtEpochMs: number;
|
||||
}>;
|
||||
|
||||
export interface BrowserManagedDownloadCapabilityResolver {
|
||||
resolve(input: Readonly<{
|
||||
resourceId: string;
|
||||
capabilityReceipt: BrowserManagedDownloadCapabilityReceipt;
|
||||
}>): BrowserDataResult<BrowserManagedDownloadCapability>;
|
||||
}
|
||||
@@ -0,0 +1,114 @@
|
||||
export type {
|
||||
BrowserDataFailure,
|
||||
BrowserDataFailureCode,
|
||||
BrowserAccountDeletionAction,
|
||||
BrowserDataAuthority,
|
||||
BrowserAccountScope,
|
||||
BrowserDataObservation,
|
||||
BrowserDataObserver,
|
||||
BrowserLogoutAction,
|
||||
BrowserDataOperation,
|
||||
BrowserPressureAction,
|
||||
BrowserDataRecovery,
|
||||
BrowserDataResult,
|
||||
BrowserStoragePolicy,
|
||||
ByteSource,
|
||||
PersistableDataClass,
|
||||
TransferProgress,
|
||||
} from "./shared.ts";
|
||||
export {
|
||||
assertValidStoragePolicy,
|
||||
isValidByteLength,
|
||||
} from "./shared.ts";
|
||||
|
||||
export type {
|
||||
BrowserManagedDownloadCapability,
|
||||
BrowserManagedDownloadCapabilityReceipt,
|
||||
BrowserManagedDownloadCapabilityResolver,
|
||||
DownloadDeliveryPort,
|
||||
DownloadOutcome,
|
||||
DownloadSource,
|
||||
DownloadStrategy,
|
||||
FileByteSource,
|
||||
FileCandidate,
|
||||
FileContentPort,
|
||||
FileInspection,
|
||||
FilePolicyIntention,
|
||||
FilePolicyKey,
|
||||
FilePolicyReference,
|
||||
FilePickerPort,
|
||||
FilePickerSupport,
|
||||
FileSelectionOutcome,
|
||||
FileSelectionLimitReduction,
|
||||
FileSelectionSource,
|
||||
FileSignatureResult,
|
||||
FileVerificationReceipt,
|
||||
LocalFileRef,
|
||||
PreviewLease,
|
||||
TransientPreviewPort,
|
||||
} from "./file.ts";
|
||||
|
||||
export type {
|
||||
IndexedDbCompareAndSwapInput,
|
||||
IndexedDbConnectionStatus,
|
||||
IndexedDbCursor,
|
||||
IndexedDbCursorKey,
|
||||
IndexedDbDatasetScope,
|
||||
IndexedDbDeleteInput,
|
||||
IndexedDbLifecycleAction,
|
||||
IndexedDbLifecycleAuthorityDecision,
|
||||
IndexedDbLifecycleAuthorityRequest,
|
||||
IndexedDbLifecycleBatchInput,
|
||||
IndexedDbLifecycleBatchReceipt,
|
||||
IndexedDbMaintenanceBatchInput,
|
||||
IndexedDbMaintenanceBatchReceipt,
|
||||
IndexedDbMaintenancePort,
|
||||
IndexedDbPage,
|
||||
IndexedDbReceiptPruneBatchReceipt,
|
||||
IndexedDbRepositoryPort,
|
||||
IndexedDbSynchronizationState,
|
||||
IndexedDbWriteReceipt,
|
||||
} from "./indexeddb-port.ts";
|
||||
|
||||
export type {
|
||||
BeginOpfsJournalTransaction,
|
||||
DurableObjectDescriptor,
|
||||
DurableObjectMaintenancePort,
|
||||
DurableObjectStorePort,
|
||||
OpenDurableObjectRequest,
|
||||
OpenedDurableObject,
|
||||
OpfsCapabilities,
|
||||
OpfsChunkReference,
|
||||
OpfsCommittedObjectPage,
|
||||
OpfsIntegrity,
|
||||
OpfsJournalMutation,
|
||||
OpfsJournalPage,
|
||||
OpfsJournalPhase,
|
||||
OpfsJournalPort,
|
||||
OpfsJournalTransaction,
|
||||
OpfsPolicyMaintenanceReport,
|
||||
OpfsPreparedObject,
|
||||
OpfsReconciliationReport,
|
||||
OpfsSensitiveMaintenanceReason,
|
||||
OpfsStorageScope,
|
||||
PutDurableObjectRequest,
|
||||
RemoveDurableObjectRequest,
|
||||
} from "./opfs-ports.ts";
|
||||
|
||||
export type {
|
||||
CachedPublicResponse,
|
||||
PublicCacheAsset,
|
||||
PublicCacheCleanupReport,
|
||||
PublicCacheHeader,
|
||||
PublicCacheInspection,
|
||||
PublicCacheReleaseManifest,
|
||||
PublicCacheReleaseSummary,
|
||||
PublicResponseCache,
|
||||
PublicResponseCacheAdminPort,
|
||||
PublicResponseCachePort,
|
||||
} from "./cache-storage-ports.ts";
|
||||
|
||||
export type {
|
||||
StorageDurabilityPort,
|
||||
StorageEstimate,
|
||||
} from "./storage-durability-port.ts";
|
||||
@@ -0,0 +1,186 @@
|
||||
import type {
|
||||
BrowserAccountScope,
|
||||
BrowserDataResult,
|
||||
BrowserStoragePolicy,
|
||||
} from "./shared.ts";
|
||||
|
||||
/**
|
||||
* Registry-issued, non-semantic dataset identity. Tokens must be random and
|
||||
* must never contain a tenant, account, user, email, domain object ID, or the
|
||||
* human-readable policy namespace.
|
||||
*/
|
||||
export type IndexedDbDatasetScope = Readonly<{
|
||||
authorityToken: string;
|
||||
namespaceToken: string;
|
||||
partitionToken: string;
|
||||
accountScope: BrowserAccountScope;
|
||||
}>;
|
||||
|
||||
export type IndexedDbSynchronizationState =
|
||||
| "PENDING"
|
||||
| "CONFIRMED";
|
||||
|
||||
export type IndexedDbConnectionStatus =
|
||||
| Readonly<{ kind: "CLOSED"; reason: "NOT_OPENED" | "VERSION_CHANGE" | "FORCED" }>
|
||||
| Readonly<{ kind: "OPENING"; targetVersion: number }>
|
||||
| Readonly<{
|
||||
kind: "BLOCKED";
|
||||
currentVersion: number;
|
||||
targetVersion: number;
|
||||
}>
|
||||
| Readonly<{ kind: "READY"; schemaVersion: number }>
|
||||
| Readonly<{ kind: "DISPOSED" }>;
|
||||
|
||||
export type IndexedDbCursorKey =
|
||||
| string
|
||||
| number
|
||||
| Date
|
||||
| ArrayBuffer
|
||||
| readonly IndexedDbCursorKey[];
|
||||
|
||||
/**
|
||||
* Opaque continuation state owned by an adapter query policy. Feature ports
|
||||
* should wrap this value if a cursor crosses a presentation or URL boundary.
|
||||
*/
|
||||
export type IndexedDbCursor = Readonly<{
|
||||
indexKey: IndexedDbCursorKey;
|
||||
primaryKey: IndexedDbCursorKey;
|
||||
}>;
|
||||
|
||||
export type IndexedDbPage<Value> = Readonly<{
|
||||
items: readonly Value[];
|
||||
nextCursor: IndexedDbCursor | null;
|
||||
}>;
|
||||
|
||||
export type IndexedDbWriteReceipt = Readonly<{
|
||||
key: string;
|
||||
revision: number;
|
||||
replayed: boolean;
|
||||
}>;
|
||||
|
||||
export type IndexedDbCompareAndSwapInput<Value> = Readonly<{
|
||||
key: string;
|
||||
value: Value;
|
||||
expectedRevision: number | null;
|
||||
idempotencyKey: string;
|
||||
/**
|
||||
* Required only for UNTIL_SYNCED datasets. The adapter never infers server
|
||||
* acknowledgement from a successful local write.
|
||||
*/
|
||||
synchronization?: IndexedDbSynchronizationState;
|
||||
signal?: AbortSignal;
|
||||
}>;
|
||||
|
||||
export type IndexedDbDeleteInput = Readonly<{
|
||||
key: string;
|
||||
expectedRevision: number;
|
||||
idempotencyKey: string;
|
||||
signal?: AbortSignal;
|
||||
}>;
|
||||
|
||||
export type IndexedDbMaintenanceBatchInput = Readonly<{
|
||||
/**
|
||||
* Hard row-count ceiling for one invocation. The adapter also observes the
|
||||
* cooperative duration budget between asynchronous storage operations.
|
||||
*/
|
||||
maxRows: number;
|
||||
maxDurationMs: number;
|
||||
signal?: AbortSignal;
|
||||
}>;
|
||||
|
||||
export type IndexedDbMaintenanceBatchReceipt = Readonly<{
|
||||
state: "MORE" | "COMPLETE";
|
||||
scannedRows: number;
|
||||
checkpointedRows: number;
|
||||
migratedRows: number;
|
||||
concurrentlyChangedRows: number;
|
||||
budgetExhausted: boolean;
|
||||
}>;
|
||||
|
||||
export type IndexedDbReceiptPruneBatchReceipt = Readonly<{
|
||||
state: "MORE" | "COMPLETE";
|
||||
scannedRows: number;
|
||||
deletedRows: number;
|
||||
budgetExhausted: boolean;
|
||||
}>;
|
||||
|
||||
export type IndexedDbLifecycleAction =
|
||||
| "SESSION_END"
|
||||
| "LOGOUT"
|
||||
| "ACCOUNT_DELETION"
|
||||
| "RETENTION_SWEEP";
|
||||
|
||||
export type IndexedDbLifecycleBatchInput = Readonly<{
|
||||
action: IndexedDbLifecycleAction;
|
||||
maxRows: number;
|
||||
maxDurationMs: number;
|
||||
signal?: AbortSignal;
|
||||
}>;
|
||||
|
||||
export type IndexedDbLifecycleBatchReceipt = Readonly<{
|
||||
state: "MORE" | "COMPLETE";
|
||||
scannedRows: number;
|
||||
deletedRows: number;
|
||||
budgetExhausted: boolean;
|
||||
}>;
|
||||
|
||||
export type IndexedDbLifecycleAuthorityRequest = Readonly<{
|
||||
action: IndexedDbLifecycleAction;
|
||||
scope: IndexedDbDatasetScope;
|
||||
storagePolicy: BrowserStoragePolicy;
|
||||
signal?: AbortSignal;
|
||||
}>;
|
||||
|
||||
export type IndexedDbLifecycleAuthorityDecision =
|
||||
| Readonly<{ authorized: false }>
|
||||
| Readonly<{
|
||||
authorized: true;
|
||||
/** Opaque, short-lived proof. It is validated and discarded, never stored. */
|
||||
proofToken: string;
|
||||
}>;
|
||||
|
||||
/**
|
||||
* Domain-neutral asynchronous repository boundary. Native IndexedDB objects,
|
||||
* object-store names, indexes and transaction callbacks remain adapter-local.
|
||||
*/
|
||||
export interface IndexedDbRepositoryPort<Value, Query> {
|
||||
open(signal?: AbortSignal): Promise<BrowserDataResult<void>>;
|
||||
read(
|
||||
key: string,
|
||||
signal?: AbortSignal,
|
||||
): Promise<BrowserDataResult<Readonly<{ value: Value; revision: number }> | null>>;
|
||||
query(
|
||||
query: Query,
|
||||
cursor?: IndexedDbCursor | null,
|
||||
signal?: AbortSignal,
|
||||
): Promise<BrowserDataResult<IndexedDbPage<Value>>>;
|
||||
compareAndSwap(
|
||||
input: IndexedDbCompareAndSwapInput<Value>,
|
||||
): Promise<BrowserDataResult<IndexedDbWriteReceipt>>;
|
||||
remove(
|
||||
input: IndexedDbDeleteInput,
|
||||
): Promise<BrowserDataResult<IndexedDbWriteReceipt>>;
|
||||
/**
|
||||
* Bounded destructive lifecycle work. Every deleting invocation is gated by
|
||||
* the composition-root authority callback; callers cannot provide proof.
|
||||
*/
|
||||
enforceLifecycleBatch(
|
||||
input: IndexedDbLifecycleBatchInput,
|
||||
): Promise<BrowserDataResult<IndexedDbLifecycleBatchReceipt>>;
|
||||
getStatus(): IndexedDbConnectionStatus;
|
||||
subscribeStatus(listener: (status: IndexedDbConnectionStatus) => void): () => void;
|
||||
close(): void;
|
||||
}
|
||||
|
||||
/**
|
||||
* Bounded, restart-safe maintenance boundary. Checkpoint keys and raw stored
|
||||
* records stay private to the adapter; callers receive aggregate progress only.
|
||||
*/
|
||||
export interface IndexedDbMaintenancePort {
|
||||
migrateCodecBatch(
|
||||
input: IndexedDbMaintenanceBatchInput,
|
||||
): Promise<BrowserDataResult<IndexedDbMaintenanceBatchReceipt>>;
|
||||
pruneExpiredReceipts(
|
||||
input: IndexedDbMaintenanceBatchInput,
|
||||
): Promise<BrowserDataResult<IndexedDbReceiptPruneBatchReceipt>>;
|
||||
}
|
||||
@@ -0,0 +1,276 @@
|
||||
import type {
|
||||
BrowserDataResult,
|
||||
BrowserStoragePolicy,
|
||||
ByteSource,
|
||||
TransferProgress,
|
||||
} from "./shared.ts";
|
||||
|
||||
export type OpfsIntegrity = Readonly<{
|
||||
algorithm: "SHA-256-TREE-V1";
|
||||
rootDigestHex: string;
|
||||
chunkSizeBytes: number;
|
||||
}>;
|
||||
|
||||
/**
|
||||
* namespaceToken and partitionToken must be random/opaque registry values.
|
||||
* Raw account IDs, email addresses and business identifiers are forbidden.
|
||||
* Only these tokens, never namespace or owner, may be used in physical paths.
|
||||
*/
|
||||
export type OpfsStorageScope = Readonly<{
|
||||
namespace: string;
|
||||
authorityToken: string;
|
||||
namespaceToken: string;
|
||||
partitionToken: string;
|
||||
}>;
|
||||
|
||||
export type DurableObjectDescriptor = Readonly<{
|
||||
objectId: string;
|
||||
scope: OpfsStorageScope;
|
||||
generation: number;
|
||||
byteLength: number;
|
||||
mediaType: string;
|
||||
createdAtEpochMs: number;
|
||||
integrity: OpfsIntegrity;
|
||||
storagePolicy: BrowserStoragePolicy;
|
||||
}>;
|
||||
|
||||
export type PutDurableObjectRequest = Readonly<{
|
||||
objectId: string;
|
||||
expectedGeneration: number | null;
|
||||
mediaType: string;
|
||||
source: ByteSource;
|
||||
signal?: AbortSignal;
|
||||
onProgress?: (progress: TransferProgress) => void;
|
||||
}>;
|
||||
|
||||
export type OpenDurableObjectRequest = Readonly<{
|
||||
objectId: string;
|
||||
generation?: number;
|
||||
signal?: AbortSignal;
|
||||
}>;
|
||||
|
||||
export type RemoveDurableObjectRequest = Readonly<{
|
||||
objectId: string;
|
||||
expectedGeneration: number;
|
||||
signal?: AbortSignal;
|
||||
}>;
|
||||
|
||||
export type OpenedDurableObject = Readonly<{
|
||||
descriptor: DurableObjectDescriptor;
|
||||
source: ByteSource;
|
||||
}>;
|
||||
|
||||
export type OpfsCapabilities = Readonly<{
|
||||
available: boolean;
|
||||
dedicatedWorkerRequired: true;
|
||||
crossContextMutationLockAvailable: boolean;
|
||||
synchronousAccessHandleAvailable: boolean;
|
||||
}>;
|
||||
|
||||
export interface DurableObjectStorePort {
|
||||
capabilities(): Promise<BrowserDataResult<OpfsCapabilities>>;
|
||||
put(
|
||||
request: PutDurableObjectRequest,
|
||||
): Promise<BrowserDataResult<DurableObjectDescriptor>>;
|
||||
open(
|
||||
request: OpenDurableObjectRequest,
|
||||
): Promise<BrowserDataResult<OpenedDurableObject>>;
|
||||
remove(
|
||||
request: RemoveDurableObjectRequest,
|
||||
): Promise<BrowserDataResult<void>>;
|
||||
}
|
||||
|
||||
export type OpfsReconciliationReport = Readonly<{
|
||||
inspectedTransactions: number;
|
||||
committedTransactions: number;
|
||||
rolledBackTransactions: number;
|
||||
cleanedTransactions: number;
|
||||
inspectedOrphanChunks: number;
|
||||
deletedOrphanChunks: number;
|
||||
orphanGcStatus:
|
||||
| "COMPLETED"
|
||||
| "DEADLINE_REACHED"
|
||||
| "STAGING_STATE_UNREADABLE";
|
||||
moreTransactionsAvailable: boolean;
|
||||
deadlineReached: boolean;
|
||||
}>;
|
||||
|
||||
export type OpfsPolicyMaintenanceReport = Readonly<{
|
||||
inspectedObjects: number;
|
||||
removedObjects: number;
|
||||
releasedBytes: number;
|
||||
moreObjectsAvailable: boolean;
|
||||
deadlineReached: boolean;
|
||||
}>;
|
||||
|
||||
export type OpfsSensitiveMaintenanceReason =
|
||||
| "LOGOUT"
|
||||
| "UNTIL_SYNCED"
|
||||
| "ACCOUNT_DELETION";
|
||||
|
||||
export interface DurableObjectMaintenancePort {
|
||||
reconcile(
|
||||
request?: Readonly<{
|
||||
budgetMs?: number;
|
||||
maxTransactions?: number;
|
||||
signal?: AbortSignal;
|
||||
}>,
|
||||
): Promise<BrowserDataResult<OpfsReconciliationReport>>;
|
||||
enforcePolicies(
|
||||
request:
|
||||
| Readonly<{
|
||||
reason: "TTL";
|
||||
budgetMs?: number;
|
||||
maxObjects?: number;
|
||||
signal?: AbortSignal;
|
||||
}>
|
||||
| Readonly<{
|
||||
reason: "LOGOUT";
|
||||
budgetMs?: number;
|
||||
maxObjects?: number;
|
||||
signal?: AbortSignal;
|
||||
}>
|
||||
| Readonly<{
|
||||
reason: "SESSION_END";
|
||||
budgetMs?: number;
|
||||
maxObjects?: number;
|
||||
signal?: AbortSignal;
|
||||
}>
|
||||
| Readonly<{
|
||||
reason: "UNTIL_SYNCED";
|
||||
budgetMs?: number;
|
||||
maxObjects?: number;
|
||||
signal?: AbortSignal;
|
||||
}>
|
||||
| Readonly<{
|
||||
reason: "ACCOUNT_DELETION";
|
||||
budgetMs?: number;
|
||||
maxObjects?: number;
|
||||
signal?: AbortSignal;
|
||||
}>
|
||||
| Readonly<{
|
||||
reason: "PRESSURE";
|
||||
targetBytesToRelease: number;
|
||||
budgetMs?: number;
|
||||
maxObjects?: number;
|
||||
signal?: AbortSignal;
|
||||
}>,
|
||||
): Promise<BrowserDataResult<OpfsPolicyMaintenanceReport>>;
|
||||
}
|
||||
|
||||
/**
|
||||
* Physical details are kept in the journal contract, not in the object-store
|
||||
* API. The IndexedDB implementation must update the journal row and logical
|
||||
* object row in the same readwrite transaction and re-check the fencing token.
|
||||
*/
|
||||
export type OpfsChunkReference = Readonly<{
|
||||
sequence: number;
|
||||
byteLength: number;
|
||||
digestHex: string;
|
||||
}>;
|
||||
|
||||
export type OpfsPreparedObject = Readonly<{
|
||||
descriptor: DurableObjectDescriptor;
|
||||
chunks: readonly OpfsChunkReference[];
|
||||
physicalSchemaVersion: 1;
|
||||
}>;
|
||||
|
||||
export type OpfsJournalMutation = "PUT" | "DELETE";
|
||||
export type OpfsJournalPhase =
|
||||
| "PREPARING"
|
||||
| "FILES_READY"
|
||||
| "COMMITTED";
|
||||
|
||||
export type OpfsJournalTransaction = Readonly<{
|
||||
transactionId: string;
|
||||
fencingToken: string;
|
||||
mutation: OpfsJournalMutation;
|
||||
scope: OpfsStorageScope;
|
||||
phase: OpfsJournalPhase;
|
||||
objectId: string;
|
||||
expectedGeneration: number | null;
|
||||
targetGeneration: number;
|
||||
targetByteLength: number;
|
||||
targetStoragePolicy: BrowserStoragePolicy;
|
||||
budgetReservation: Readonly<{
|
||||
namespace: string;
|
||||
reservedBytes: number;
|
||||
hardBudgetBytes: number;
|
||||
}>;
|
||||
startedAtEpochMs: number;
|
||||
preparedObject?: OpfsPreparedObject;
|
||||
}>;
|
||||
|
||||
export type BeginOpfsJournalTransaction = Readonly<{
|
||||
transactionId: string;
|
||||
mutation: OpfsJournalMutation;
|
||||
scope: OpfsStorageScope;
|
||||
objectId: string;
|
||||
expectedGeneration: number | null;
|
||||
targetGeneration: number;
|
||||
targetByteLength: number;
|
||||
targetStoragePolicy: BrowserStoragePolicy;
|
||||
startedAtEpochMs: number;
|
||||
}>;
|
||||
|
||||
export type OpfsJournalPage = Readonly<{
|
||||
transactions: readonly OpfsJournalTransaction[];
|
||||
moreAvailable: boolean;
|
||||
}>;
|
||||
|
||||
export type OpfsCommittedObjectPage = Readonly<{
|
||||
objects: readonly OpfsPreparedObject[];
|
||||
nextObjectId: string | null;
|
||||
moreAvailable: boolean;
|
||||
}>;
|
||||
|
||||
export interface OpfsJournalPort {
|
||||
getCommittedObject(
|
||||
scope: OpfsStorageScope,
|
||||
objectId: string,
|
||||
): Promise<BrowserDataResult<OpfsPreparedObject | null>>;
|
||||
begin(
|
||||
transaction: BeginOpfsJournalTransaction,
|
||||
): Promise<BrowserDataResult<OpfsJournalTransaction>>;
|
||||
markFilesReady(
|
||||
transactionId: string,
|
||||
fencingToken: string,
|
||||
preparedObject: OpfsPreparedObject,
|
||||
): Promise<BrowserDataResult<OpfsJournalTransaction>>;
|
||||
/**
|
||||
* Atomically publishes preparedObject and advances the journal to COMMITTED.
|
||||
*/
|
||||
commitPut(
|
||||
transactionId: string,
|
||||
fencingToken: string,
|
||||
): Promise<BrowserDataResult<OpfsJournalTransaction>>;
|
||||
/**
|
||||
* Atomically removes the logical object and advances the journal to COMMITTED.
|
||||
*/
|
||||
commitDelete(
|
||||
transactionId: string,
|
||||
fencingToken: string,
|
||||
): Promise<BrowserDataResult<OpfsJournalTransaction>>;
|
||||
complete(
|
||||
transactionId: string,
|
||||
fencingToken: string,
|
||||
): Promise<BrowserDataResult<void>>;
|
||||
rollback(
|
||||
transactionId: string,
|
||||
fencingToken: string,
|
||||
): Promise<BrowserDataResult<void>>;
|
||||
listIncomplete(
|
||||
limit: number,
|
||||
): Promise<BrowserDataResult<OpfsJournalPage>>;
|
||||
listCommittedObjects(
|
||||
request: Readonly<{
|
||||
scope: OpfsStorageScope;
|
||||
afterObjectId?: string;
|
||||
limit: number;
|
||||
}>,
|
||||
): Promise<BrowserDataResult<OpfsCommittedObjectPage>>;
|
||||
isChunkReferenced(
|
||||
scope: OpfsStorageScope,
|
||||
digestHex: string,
|
||||
): Promise<BrowserDataResult<boolean>>;
|
||||
}
|
||||
@@ -0,0 +1,238 @@
|
||||
import type { Result } from "../../result.ts";
|
||||
|
||||
export type BrowserDataFailureCode =
|
||||
| "ABORTED"
|
||||
| "BLOCKED"
|
||||
| "CONFLICT"
|
||||
| "CORRUPT_DATA"
|
||||
| "EXPIRED_RESOURCE"
|
||||
| "INTEGRITY_FAILED"
|
||||
| "INVALID_INPUT"
|
||||
| "LIMIT_EXCEEDED"
|
||||
| "MIGRATION_FAILED"
|
||||
| "NOT_FOUND"
|
||||
| "NOT_READABLE"
|
||||
| "PERMISSION_DENIED"
|
||||
| "POLICY_REJECTED"
|
||||
| "QUOTA_EXCEEDED"
|
||||
| "STALE_RESULT"
|
||||
| "STORAGE_EVICTED"
|
||||
| "UNAVAILABLE"
|
||||
| "UNSUPPORTED";
|
||||
|
||||
export type BrowserDataOperation =
|
||||
| "CACHE_ACTIVATE"
|
||||
| "CACHE_DELETE"
|
||||
| "CACHE_LOOKUP"
|
||||
| "CACHE_STAGE"
|
||||
| "DOWNLOAD"
|
||||
| "FILE_INSPECT"
|
||||
| "FILE_READ"
|
||||
| "FILE_SELECT"
|
||||
| "IMAGE_RESOLVE"
|
||||
| "INDEXEDDB_MIGRATE"
|
||||
| "INDEXEDDB_OPEN"
|
||||
| "INDEXEDDB_READ"
|
||||
| "INDEXEDDB_WRITE"
|
||||
| "OBJECT_DELETE"
|
||||
| "OBJECT_READ"
|
||||
| "OBJECT_RECONCILE"
|
||||
| "OBJECT_WRITE"
|
||||
| "PRESIGNED_TRANSFER"
|
||||
| "PREVIEW"
|
||||
| "STORAGE_ESTIMATE"
|
||||
| "STORAGE_PERSIST"
|
||||
| "UPLOAD_ABORT"
|
||||
| "UPLOAD_COMPLETE"
|
||||
| "UPLOAD_PART"
|
||||
| "UPLOAD_RECONCILE"
|
||||
| "UPLOAD_SESSION";
|
||||
|
||||
export type BrowserDataRecovery =
|
||||
| "NONE"
|
||||
| "RETRY"
|
||||
| "REOPEN"
|
||||
| "RESELECT"
|
||||
| "RELOAD_OTHER_CONTEXTS"
|
||||
| "READ_ONLY"
|
||||
| "ONLINE_ONLY"
|
||||
| "REHYDRATE"
|
||||
| "EXPORT_REQUIRED"
|
||||
| "REISSUE_CAPABILITY"
|
||||
| "RESUME"
|
||||
| "RESTART"
|
||||
| "RECONCILE";
|
||||
|
||||
/**
|
||||
* Closed, telemetry-safe failure. Native exception messages, paths, record
|
||||
* keys, URLs, file names and data values must not cross the adapter boundary.
|
||||
*/
|
||||
export type BrowserDataFailure = Readonly<{
|
||||
code: BrowserDataFailureCode;
|
||||
operation: BrowserDataOperation;
|
||||
retryable: boolean;
|
||||
recovery: BrowserDataRecovery;
|
||||
}>;
|
||||
|
||||
export type BrowserDataResult<Value> = Result<Value, BrowserDataFailure>;
|
||||
|
||||
export type BrowserDataObservation = Readonly<{
|
||||
operation: BrowserDataOperation;
|
||||
outcome: "SUCCEEDED" | "FAILED" | "DEGRADED";
|
||||
failureCode?: BrowserDataFailureCode;
|
||||
durationBucket?: "LT100MS" | "100_TO_499MS" | "500_TO_1999MS" | "GTE2000MS";
|
||||
byteBucket?: "ZERO" | "LT1MIB" | "1_TO_9MIB" | "10_TO_99MIB" | "GTE100MIB";
|
||||
countBucket?: "ZERO" | "ONE" | "TWO_TO_TEN" | "ELEVEN_TO_HUNDRED" | "GT_HUNDRED";
|
||||
}>;
|
||||
|
||||
export interface BrowserDataObserver {
|
||||
record(observation: BrowserDataObservation): void;
|
||||
}
|
||||
|
||||
export type TransferProgress = Readonly<{
|
||||
phase:
|
||||
| "VALIDATING"
|
||||
| "PREPARING"
|
||||
| "TRANSFERRING"
|
||||
| "VERIFYING"
|
||||
| "FINALIZING";
|
||||
transferredBytes: number;
|
||||
totalBytes: number | null;
|
||||
}>;
|
||||
|
||||
/**
|
||||
* Technology-neutral byte stream. Implementations must validate every emitted
|
||||
* chunk, honor AbortSignal between chunks and close native/runtime failures
|
||||
* into BrowserDataResult. A consumer must stop after the first failed chunk.
|
||||
*/
|
||||
export interface ByteSource {
|
||||
readonly byteLength: number | null;
|
||||
stream(
|
||||
signal: AbortSignal,
|
||||
): AsyncIterable<BrowserDataResult<Uint8Array>>;
|
||||
}
|
||||
|
||||
export type PersistableDataClass =
|
||||
| "PUBLIC"
|
||||
| "INTERNAL"
|
||||
| "PERSONAL"
|
||||
| "CONFIDENTIAL";
|
||||
|
||||
export type BrowserDataAuthority =
|
||||
| "SERVER"
|
||||
| "LOCAL_FIRST"
|
||||
| "RECONSTRUCTABLE";
|
||||
|
||||
export type BrowserAccountScope =
|
||||
| "ORIGIN_SHARED"
|
||||
| "OPAQUE_PARTITION";
|
||||
|
||||
export type BrowserLogoutAction =
|
||||
| "KEEP_ORIGIN_SHARED"
|
||||
| "PURGE_PARTITION"
|
||||
| "EXPORT_THEN_PURGE";
|
||||
|
||||
export type BrowserAccountDeletionAction =
|
||||
| "KEEP_ORIGIN_SHARED"
|
||||
| "PURGE_PARTITION";
|
||||
|
||||
export type BrowserPressureAction =
|
||||
| "EVICT_RECONSTRUCTABLE"
|
||||
| "RETAIN";
|
||||
|
||||
export type BrowserStoragePolicy = Readonly<{
|
||||
/** Governance owner/team identifier, never an account or user ID. */
|
||||
owner: string;
|
||||
namespace: string;
|
||||
classification: PersistableDataClass;
|
||||
authority: BrowserDataAuthority;
|
||||
accountScope: BrowserAccountScope;
|
||||
retention:
|
||||
| Readonly<{ kind: "SESSION" }>
|
||||
| Readonly<{ kind: "TTL"; maxAgeMs: number }>
|
||||
| Readonly<{ kind: "UNTIL_SYNCED" }>
|
||||
| Readonly<{ kind: "EXPLICIT_DELETE" }>;
|
||||
softBudgetBytes: number;
|
||||
hardBudgetBytes: number;
|
||||
evictionPriority: "RECONSTRUCTABLE" | "SYNCED_COPY" | "USER_AUTHORED";
|
||||
logoutAction: BrowserLogoutAction;
|
||||
accountDeletionAction: BrowserAccountDeletionAction;
|
||||
pressureAction: BrowserPressureAction;
|
||||
unavailableFallback: "ONLINE_ONLY" | "READ_ONLY" | "EXPORT_REQUIRED";
|
||||
}>;
|
||||
|
||||
export function isValidByteLength(value: number): boolean {
|
||||
return Number.isSafeInteger(value) && value >= 0;
|
||||
}
|
||||
|
||||
export function assertValidStoragePolicy(
|
||||
policy: BrowserStoragePolicy,
|
||||
): void {
|
||||
const safeRegistryIdentifier =
|
||||
/^[A-Za-z0-9][A-Za-z0-9._:-]{0,127}$/u;
|
||||
if (
|
||||
!policy ||
|
||||
typeof policy !== "object" ||
|
||||
typeof policy.owner !== "string" ||
|
||||
!safeRegistryIdentifier.test(policy.owner) ||
|
||||
typeof policy.namespace !== "string" ||
|
||||
!safeRegistryIdentifier.test(policy.namespace) ||
|
||||
!["PUBLIC", "INTERNAL", "PERSONAL", "CONFIDENTIAL"].includes(
|
||||
policy.classification,
|
||||
) ||
|
||||
!["SERVER", "LOCAL_FIRST", "RECONSTRUCTABLE"].includes(
|
||||
policy.authority,
|
||||
) ||
|
||||
!["ORIGIN_SHARED", "OPAQUE_PARTITION"].includes(
|
||||
policy.accountScope,
|
||||
) ||
|
||||
!policy.retention ||
|
||||
typeof policy.retention !== "object" ||
|
||||
!["SESSION", "TTL", "UNTIL_SYNCED", "EXPLICIT_DELETE"].includes(
|
||||
policy.retention.kind,
|
||||
) ||
|
||||
!isValidByteLength(policy.softBudgetBytes) ||
|
||||
!isValidByteLength(policy.hardBudgetBytes) ||
|
||||
policy.hardBudgetBytes === 0 ||
|
||||
policy.softBudgetBytes > policy.hardBudgetBytes ||
|
||||
!["RECONSTRUCTABLE", "SYNCED_COPY", "USER_AUTHORED"].includes(
|
||||
policy.evictionPriority,
|
||||
) ||
|
||||
![
|
||||
"KEEP_ORIGIN_SHARED",
|
||||
"PURGE_PARTITION",
|
||||
"EXPORT_THEN_PURGE",
|
||||
].includes(policy.logoutAction) ||
|
||||
!["KEEP_ORIGIN_SHARED", "PURGE_PARTITION"].includes(
|
||||
policy.accountDeletionAction,
|
||||
) ||
|
||||
!["EVICT_RECONSTRUCTABLE", "RETAIN"].includes(
|
||||
policy.pressureAction,
|
||||
) ||
|
||||
!["ONLINE_ONLY", "READ_ONLY", "EXPORT_REQUIRED"].includes(
|
||||
policy.unavailableFallback,
|
||||
) ||
|
||||
(policy.retention.kind === "TTL" &&
|
||||
(!Number.isSafeInteger(policy.retention.maxAgeMs) ||
|
||||
policy.retention.maxAgeMs < 1)) ||
|
||||
(policy.accountScope === "ORIGIN_SHARED" &&
|
||||
(policy.logoutAction !== "KEEP_ORIGIN_SHARED" ||
|
||||
policy.accountDeletionAction !== "KEEP_ORIGIN_SHARED")) ||
|
||||
(policy.accountScope === "OPAQUE_PARTITION" &&
|
||||
(policy.logoutAction === "KEEP_ORIGIN_SHARED" ||
|
||||
policy.accountDeletionAction !== "PURGE_PARTITION")) ||
|
||||
(["PERSONAL", "CONFIDENTIAL"].includes(policy.classification) &&
|
||||
policy.accountScope !== "OPAQUE_PARTITION") ||
|
||||
(policy.accountScope === "OPAQUE_PARTITION" &&
|
||||
(policy.retention.kind === "UNTIL_SYNCED" ||
|
||||
(policy.authority === "LOCAL_FIRST" &&
|
||||
policy.unavailableFallback === "EXPORT_REQUIRED")) &&
|
||||
policy.logoutAction !== "EXPORT_THEN_PURGE") ||
|
||||
(policy.pressureAction === "EVICT_RECONSTRUCTABLE" &&
|
||||
(policy.authority !== "RECONSTRUCTABLE" ||
|
||||
policy.evictionPriority !== "RECONSTRUCTABLE" ||
|
||||
policy.retention.kind === "EXPLICIT_DELETE"))
|
||||
) {
|
||||
throw new TypeError("Browser storage policy is invalid.");
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
import type { BrowserDataResult } from "./shared.ts";
|
||||
|
||||
export type StorageEstimate = Readonly<{
|
||||
usageBytes: number | null;
|
||||
quotaBytes: number | null;
|
||||
/**
|
||||
* null means the engine did not expose a persistence-state query. It must
|
||||
* not be collapsed into a false "not persisted" claim.
|
||||
*/
|
||||
persisted: boolean | null;
|
||||
pressure: "UNKNOWN" | "NORMAL" | "PRESSURE" | "CRITICAL";
|
||||
}>;
|
||||
|
||||
export interface StorageDurabilityPort {
|
||||
inspect(signal?: AbortSignal): Promise<BrowserDataResult<StorageEstimate>>;
|
||||
|
||||
requestPersistence(input: {
|
||||
reason: "PROTECT_UNSYNCED_USER_DATA";
|
||||
userInitiated: true;
|
||||
signal?: AbortSignal;
|
||||
}): Promise<BrowserDataResult<"GRANTED" | "DENIED">>;
|
||||
}
|
||||
@@ -0,0 +1,36 @@
|
||||
import type { Result } from "../../result.ts";
|
||||
import type { AppFailure } from "../../../contracts/errors.ts";
|
||||
|
||||
export type BrowserRpcCallContext = Readonly<{
|
||||
signal?: AbortSignal;
|
||||
idempotencyKey?: string;
|
||||
}>;
|
||||
|
||||
/**
|
||||
* A feature gateway binds a semantic operation during composition and exposes
|
||||
* only this typed port to its use case. Generated services, messages, endpoint
|
||||
* IDs and transport metadata remain adapter-private.
|
||||
*/
|
||||
export type BrowserRpcUnaryPort<Input, Output> = Readonly<{
|
||||
execute(
|
||||
input: Input,
|
||||
context?: BrowserRpcCallContext,
|
||||
): Promise<Result<Output, AppFailure>>;
|
||||
}>;
|
||||
|
||||
/**
|
||||
* Server streams remain operation-bound outbound results. They are not a
|
||||
* runtime-wide event bus and do not expose protocol frames or generated
|
||||
* messages to application callers.
|
||||
*/
|
||||
export type BrowserRpcServerStreamPort<Input, Event> = Readonly<{
|
||||
open(
|
||||
input: Input,
|
||||
context?: BrowserRpcCallContext,
|
||||
): AsyncIterable<Result<Event, AppFailure>>;
|
||||
}>;
|
||||
|
||||
export type BrowserRpcGenerationFence<Token = unknown> = Readonly<{
|
||||
capture(): Token;
|
||||
isCurrent(token: Token): boolean;
|
||||
}>;
|
||||
@@ -0,0 +1,6 @@
|
||||
export type {
|
||||
BrowserRpcCallContext,
|
||||
BrowserRpcGenerationFence,
|
||||
BrowserRpcServerStreamPort,
|
||||
BrowserRpcUnaryPort,
|
||||
} from "./browser-rpc.ts";
|
||||
@@ -0,0 +1,32 @@
|
||||
declare const authorizedDownloadCapabilityBrand: unique symbol;
|
||||
declare const authorizedDownloadCapabilityReceiptBrand: unique symbol;
|
||||
|
||||
/**
|
||||
* Telemetry-safe server-issued identifier. It is not a URL, credential,
|
||||
* object-store key or authorization token.
|
||||
*/
|
||||
export type AuthorizedDownloadCapabilityReceipt = string & {
|
||||
readonly [authorizedDownloadCapabilityReceiptBrand]:
|
||||
"AuthorizedDownloadCapabilityReceipt";
|
||||
};
|
||||
|
||||
/**
|
||||
* Opaque GET-only download handle shared by the file-delivery and transfer
|
||||
* ports. The adapter owns the corresponding URL/query/header binding in an
|
||||
* identity vault, so structurally equal caller-created objects are rejected.
|
||||
*/
|
||||
export type AuthorizedDownloadCapability = Readonly<{
|
||||
capabilityReceipt: AuthorizedDownloadCapabilityReceipt;
|
||||
method: "GET";
|
||||
binding: Readonly<{
|
||||
kind: "DOWNLOAD";
|
||||
resourceId: string;
|
||||
}>;
|
||||
mediaType: string;
|
||||
byteLength: number;
|
||||
maxBytes: number;
|
||||
expectedSha256: string;
|
||||
expiresAtEpochMs: number;
|
||||
readonly [authorizedDownloadCapabilityBrand]:
|
||||
"AuthorizedDownloadCapability";
|
||||
}>;
|
||||
@@ -0,0 +1,183 @@
|
||||
import type { BrowserDataResult } from "../browser-file-storage/shared.ts";
|
||||
|
||||
declare const imageAssetReferenceBrand: unique symbol;
|
||||
declare const imagePresetReferenceBrand: unique symbol;
|
||||
|
||||
export type ImageRasterMediaType =
|
||||
| "image/avif"
|
||||
| "image/jpeg"
|
||||
| "image/png"
|
||||
| "image/webp";
|
||||
|
||||
export type ImageOutputFormat = "avif" | "jpeg" | "png" | "webp";
|
||||
export type ImageFit = "contain" | "cover" | "fill" | "inside" | "outside";
|
||||
|
||||
/**
|
||||
* Identity capability backed by an adapter-owned WeakMap. A structurally equal
|
||||
* object or a reference issued by another runtime must be rejected.
|
||||
*/
|
||||
export type ImageAssetReference = Readonly<{
|
||||
readonly [imageAssetReferenceBrand]: "ImageAssetReference";
|
||||
}>;
|
||||
|
||||
/**
|
||||
* Composition-issued named preset reference. Presentation cannot submit
|
||||
* width, height, DPR, quality, format, URL or query overrides.
|
||||
*/
|
||||
export type ImagePresetReference = Readonly<{
|
||||
presetKey: string;
|
||||
intention: string;
|
||||
readonly [imagePresetReferenceBrand]: "ImagePresetReference";
|
||||
}>;
|
||||
|
||||
export type PublicImmutableImageAsset = Readonly<{
|
||||
kind: "ALLOWLISTED_PUBLIC";
|
||||
originKey: string;
|
||||
assetId: string;
|
||||
revision: string;
|
||||
mediaType: ImageRasterMediaType;
|
||||
contentKind: "RASTER_STATIC";
|
||||
intrinsicWidth: number;
|
||||
intrinsicHeight: number;
|
||||
}>;
|
||||
|
||||
/**
|
||||
* Server-issued descriptor for private signed delivery. It contains no URL or
|
||||
* request headers. The signature covers every immutable field and the exact
|
||||
* set of registry-owned preset binding IDs.
|
||||
*/
|
||||
export type BackendIssuedImageAsset = Readonly<{
|
||||
kind: "BACKEND_ISSUED_PRIVATE";
|
||||
issuer: string;
|
||||
originKey: string;
|
||||
assetId: string;
|
||||
revision: string;
|
||||
mediaType: ImageRasterMediaType;
|
||||
contentKind: "RASTER_STATIC";
|
||||
intrinsicWidth: number;
|
||||
intrinsicHeight: number;
|
||||
capabilityId: string;
|
||||
issuedAtEpochMs: number;
|
||||
expiresAtEpochMs: number;
|
||||
allowedPresetBindingIds: readonly string[];
|
||||
signature: Readonly<{
|
||||
algorithm: "ECDSA_P256_SHA256";
|
||||
keyId: string;
|
||||
capabilityBindingDigestHex: string;
|
||||
valueBase64Url: string;
|
||||
}>;
|
||||
}>;
|
||||
|
||||
export type ImageCapabilityVerificationRequest = Readonly<{
|
||||
algorithm: "ECDSA_P256_SHA256";
|
||||
keyId: string;
|
||||
canonicalPayload: Uint8Array;
|
||||
signatureBase64Url: string;
|
||||
}>;
|
||||
|
||||
export interface ImageCapabilityVerifier {
|
||||
/** Exact membership check against the verifier's immutable key registry. */
|
||||
acceptsKey(keyId: string): boolean;
|
||||
verify(
|
||||
request: ImageCapabilityVerificationRequest,
|
||||
): Promise<boolean>;
|
||||
}
|
||||
|
||||
export interface ImageAssetAcceptancePort {
|
||||
acceptPublicImmutable(
|
||||
descriptor: PublicImmutableImageAsset,
|
||||
): BrowserDataResult<ImageAssetReference>;
|
||||
acceptBackendIssued(
|
||||
descriptor: BackendIssuedImageAsset,
|
||||
options?: Readonly<{ signal?: AbortSignal }>,
|
||||
): Promise<BrowserDataResult<ImageAssetReference>>;
|
||||
}
|
||||
|
||||
export type ImageDeliveryClass =
|
||||
| "PUBLIC_IMMUTABLE"
|
||||
| "PRIVATE_SIGNED";
|
||||
|
||||
export type ImageProbeRequest = Readonly<{
|
||||
absoluteUrl: string;
|
||||
expectedMediaType: ImageRasterMediaType;
|
||||
expectedWidth: number;
|
||||
expectedHeight: number;
|
||||
maxEncodedBytes: number;
|
||||
maxDecodedPixels: number;
|
||||
maxDecodedBytes: number;
|
||||
delivery: ImageDeliveryClass;
|
||||
minimumPublicMaxAgeSeconds: number;
|
||||
referrerPolicy: "no-referrer" | "strict-origin-when-cross-origin";
|
||||
signal: AbortSignal;
|
||||
}>;
|
||||
|
||||
export type ImageProbeReceipt = Readonly<{
|
||||
absoluteUrl: string;
|
||||
mediaType: ImageRasterMediaType;
|
||||
encodedBytes: number;
|
||||
decodedWidth: number;
|
||||
decodedHeight: number;
|
||||
}>;
|
||||
|
||||
/**
|
||||
* Optional browser-native seam. Implementations must bound the encoded body
|
||||
* before buffering and close the decoded ImageBitmap after inspecting it.
|
||||
*/
|
||||
export interface ImageResourceProbePort {
|
||||
probe(
|
||||
request: ImageProbeRequest,
|
||||
): Promise<BrowserDataResult<ImageProbeReceipt>>;
|
||||
}
|
||||
|
||||
export type ImagePresentationSource = Readonly<{
|
||||
type: ImageRasterMediaType;
|
||||
srcSet: string;
|
||||
}>;
|
||||
|
||||
export type ImagePresentationDescriptor = Readonly<{
|
||||
src: string;
|
||||
srcSet: string;
|
||||
sources: readonly ImagePresentationSource[];
|
||||
sizes: string;
|
||||
width: number;
|
||||
height: number;
|
||||
fallbackMediaType: ImageRasterMediaType;
|
||||
loading: "eager" | "lazy";
|
||||
decoding: "async" | "sync";
|
||||
fetchPriority: "high" | "low" | "auto";
|
||||
referrerPolicy: "no-referrer" | "strict-origin-when-cross-origin";
|
||||
crossOrigin: "anonymous";
|
||||
delivery: Readonly<{
|
||||
class: ImageDeliveryClass;
|
||||
assetVersion: string;
|
||||
browserCache: "PUBLIC_IMMUTABLE" | "NO_STORE";
|
||||
sharedCache: "PUBLIC_IMMUTABLE" | "FORBIDDEN";
|
||||
purge:
|
||||
| "REVISION_ROLLOVER"
|
||||
| "CAPABILITY_REVOCATION_OR_EXPIRY";
|
||||
expiresAtEpochMs: number | null;
|
||||
}>;
|
||||
decodeBudget: Readonly<{
|
||||
maximumCandidatePixels: number;
|
||||
maximumDecodedBytes: number;
|
||||
maximumEncodedBytes: number;
|
||||
}>;
|
||||
}>;
|
||||
|
||||
export interface ImageCdnPresentationPort {
|
||||
resolve(request: Readonly<{
|
||||
asset: ImageAssetReference;
|
||||
preset: ImagePresetReference;
|
||||
signal?: AbortSignal;
|
||||
}>): Promise<BrowserDataResult<ImagePresentationDescriptor>>;
|
||||
}
|
||||
|
||||
export type ImageCdnRuntime = Readonly<{
|
||||
assets: ImageAssetAcceptancePort;
|
||||
presentation: ImageCdnPresentationPort;
|
||||
/**
|
||||
* Terminal and idempotent. Aborts in-flight verification/probing, revokes
|
||||
* every issued reference and makes later accept/resolve calls unavailable.
|
||||
*/
|
||||
close(): void;
|
||||
}>;
|
||||
@@ -0,0 +1,68 @@
|
||||
export type {
|
||||
AuthorizedDownloadCapability,
|
||||
AuthorizedDownloadCapabilityReceipt,
|
||||
} from "./authorized-download.ts";
|
||||
|
||||
export type {
|
||||
BackendIssuedImageAsset,
|
||||
ImageAssetAcceptancePort,
|
||||
ImageAssetReference,
|
||||
ImageCapabilityVerificationRequest,
|
||||
ImageCapabilityVerifier,
|
||||
ImageCdnPresentationPort,
|
||||
ImageCdnRuntime,
|
||||
ImageDeliveryClass,
|
||||
ImageFit,
|
||||
ImageOutputFormat,
|
||||
ImagePresentationDescriptor,
|
||||
ImagePresentationSource,
|
||||
ImagePresetReference,
|
||||
ImageProbeReceipt,
|
||||
ImageProbeRequest,
|
||||
ImageRasterMediaType,
|
||||
ImageResourceProbePort,
|
||||
PublicImmutableImageAsset,
|
||||
} from "./image-cdn.ts";
|
||||
|
||||
export type {
|
||||
PresignedDownloadByteSource,
|
||||
PresignedDownloadCapability,
|
||||
PresignedDownloadSourcePort,
|
||||
PresignedTransferBinding,
|
||||
PresignedTransferCapability,
|
||||
PresignedTransferCapabilityProvider,
|
||||
PresignedTransferCapabilityReceipt,
|
||||
PresignedTransferMethod,
|
||||
PresignedTransferReplayGuard,
|
||||
PresignedUploadPartCapability,
|
||||
PresignedUploadPartCapabilityProvider,
|
||||
PresignedUploadPartOutcome,
|
||||
PresignedUploadPartPort,
|
||||
} from "./presigned-transfer.ts";
|
||||
|
||||
export type {
|
||||
ActiveUploadStatus,
|
||||
QuarantinedUpload,
|
||||
ResumableUploadCheckpoint,
|
||||
ResumableUploadCheckpointAdmin,
|
||||
ResumableUploadCheckpointStore,
|
||||
ResumableUploadControlPlane,
|
||||
ResumableUploadPort,
|
||||
ResumableUploadRequest,
|
||||
ResumableUploadSource,
|
||||
UploadAbortOutcome,
|
||||
UploadFileFingerprint,
|
||||
UploadPartCapability,
|
||||
UploadPartDescriptor,
|
||||
UploadPartExecutor,
|
||||
UploadPartReceipt,
|
||||
UploadProviderFailure,
|
||||
UploadProviderResult,
|
||||
UploadRangeReader,
|
||||
UploadSession,
|
||||
UploadSessionStatus,
|
||||
} from "./resumable-upload.ts";
|
||||
export {
|
||||
RESUMABLE_UPLOAD_PROTOCOL,
|
||||
type ResumableUploadProtocol,
|
||||
} from "./resumable-upload.ts";
|
||||
@@ -0,0 +1,151 @@
|
||||
import type { FileByteSource } from "../browser-file-storage/file.ts";
|
||||
import type { BrowserDataResult } from "../browser-file-storage/shared.ts";
|
||||
import type {
|
||||
AuthorizedDownloadCapability,
|
||||
AuthorizedDownloadCapabilityReceipt,
|
||||
} from "./authorized-download.ts";
|
||||
import type { ResumableUploadProtocol } from "./resumable-upload.ts";
|
||||
|
||||
declare const presignedTransferCapabilityBrand: unique symbol;
|
||||
|
||||
/**
|
||||
* Server-issued, telemetry-safe identifier. It is not a URL, credential,
|
||||
* object-store key or authorization token.
|
||||
*/
|
||||
export type PresignedTransferCapabilityReceipt =
|
||||
AuthorizedDownloadCapabilityReceipt;
|
||||
|
||||
export type PresignedTransferMethod = "GET" | "PUT";
|
||||
|
||||
export type PresignedTransferBinding =
|
||||
| Readonly<{
|
||||
kind: "DOWNLOAD";
|
||||
resourceId: string;
|
||||
}>
|
||||
| Readonly<{
|
||||
kind: "UPLOAD_PART";
|
||||
protocol: ResumableUploadProtocol;
|
||||
sessionId: string;
|
||||
requestBindingSha256: string;
|
||||
/**
|
||||
* SHA-256 over the canonical session, request and whole-file fingerprint
|
||||
* binding. The raw session fields remain owned by the upload control
|
||||
* plane; they must never be smuggled into resourceId.
|
||||
*/
|
||||
uploadBindingSha256: string;
|
||||
partNumber: number;
|
||||
offset: number;
|
||||
idempotencyKey: string;
|
||||
}>;
|
||||
|
||||
/**
|
||||
* Opaque capability handle crossing the application boundary. The adapter owns
|
||||
* the corresponding URL, query values and request headers in an in-memory
|
||||
* identity vault. Implementations must reject structurally equal or fabricated
|
||||
* handles, even if every visible field matches.
|
||||
*/
|
||||
export type PresignedDownloadCapability = AuthorizedDownloadCapability;
|
||||
|
||||
export type PresignedUploadPartCapability = Readonly<{
|
||||
capabilityReceipt: PresignedTransferCapabilityReceipt;
|
||||
method: "PUT";
|
||||
binding: Extract<
|
||||
PresignedTransferBinding,
|
||||
Readonly<{ kind: "UPLOAD_PART" }>
|
||||
>;
|
||||
mediaType: string;
|
||||
byteLength: number;
|
||||
maxBytes: number;
|
||||
expectedSha256: string;
|
||||
expiresAtEpochMs: number;
|
||||
readonly [presignedTransferCapabilityBrand]:
|
||||
"PresignedTransferCapability";
|
||||
}>;
|
||||
|
||||
export type PresignedTransferCapability =
|
||||
| PresignedDownloadCapability
|
||||
| PresignedUploadPartCapability;
|
||||
|
||||
export interface PresignedTransferCapabilityProvider {
|
||||
/**
|
||||
* Calls a composition-owned backend/BFF capability endpoint. Callers choose
|
||||
* only an opaque resource ID; they cannot supply a transfer URL or headers.
|
||||
*/
|
||||
issueDownload(input: Readonly<{
|
||||
resourceId: string;
|
||||
signal: AbortSignal;
|
||||
}>): Promise<BrowserDataResult<PresignedDownloadCapability>>;
|
||||
}
|
||||
|
||||
export interface PresignedUploadPartCapabilityProvider {
|
||||
issueUploadPart(input: Readonly<{
|
||||
sessionId: string;
|
||||
requestBindingSha256: string;
|
||||
uploadBindingSha256: string;
|
||||
partNumber: number;
|
||||
offset: number;
|
||||
byteLength: number;
|
||||
checksumSha256: string;
|
||||
mediaType: string;
|
||||
idempotencyKey: string;
|
||||
signal: AbortSignal;
|
||||
}>): Promise<BrowserDataResult<PresignedUploadPartCapability>>;
|
||||
}
|
||||
|
||||
/**
|
||||
* Atomic local replay seam. The server/object-store capability must also
|
||||
* enforce single use or equivalent idempotency because a browser guard is not
|
||||
* an authorization boundary.
|
||||
*/
|
||||
export interface PresignedTransferReplayGuard {
|
||||
claim(
|
||||
capability: PresignedTransferCapability,
|
||||
): BrowserDataResult<true>;
|
||||
}
|
||||
|
||||
/**
|
||||
* Successful exhaustion proves length and SHA-256 before the terminal success
|
||||
* of the closed-Result stream. Consumers must not commit a destination until
|
||||
* the iterable finishes without a failure result.
|
||||
*/
|
||||
export type PresignedDownloadByteSource = FileByteSource &
|
||||
Readonly<{
|
||||
byteLength: number;
|
||||
capability: PresignedDownloadCapability;
|
||||
integrity: "VERIFIED_ON_SUCCESSFUL_EXHAUSTION";
|
||||
}>;
|
||||
|
||||
export interface PresignedDownloadSourcePort {
|
||||
open(input: Readonly<{
|
||||
resourceId: string;
|
||||
capability: PresignedDownloadCapability;
|
||||
signal: AbortSignal;
|
||||
}>): Promise<BrowserDataResult<PresignedDownloadByteSource>>;
|
||||
}
|
||||
|
||||
export type PresignedUploadPartOutcome = Readonly<{
|
||||
bytesWritten: number;
|
||||
checksumSha256: string;
|
||||
/**
|
||||
* Non-authorizing object-store acknowledgement (for example a normalized
|
||||
* ETag). It is safe to persist only as part of the exact completed-part
|
||||
* binding and must never be reused as a transfer capability.
|
||||
*/
|
||||
receiptToken: string;
|
||||
}>;
|
||||
|
||||
export interface PresignedUploadPartPort {
|
||||
put(input: Readonly<{
|
||||
capability: PresignedUploadPartCapability;
|
||||
sessionId: string;
|
||||
requestBindingSha256: string;
|
||||
uploadBindingSha256: string;
|
||||
partNumber: number;
|
||||
offset: number;
|
||||
byteLength: number;
|
||||
checksumSha256: string;
|
||||
idempotencyKey: string;
|
||||
bytes: Uint8Array;
|
||||
signal: AbortSignal;
|
||||
}>): Promise<BrowserDataResult<PresignedUploadPartOutcome>>;
|
||||
}
|
||||
@@ -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" }>>>;
|
||||
}
|
||||
@@ -0,0 +1,4 @@
|
||||
export type ClockPort = Readonly<{
|
||||
now(): number;
|
||||
sleep(milliseconds: number, signal?: AbortSignal): Promise<void>;
|
||||
}>;
|
||||
@@ -0,0 +1,5 @@
|
||||
import type { DiagnosticRecordInput } from "../../contracts/diagnostics.ts";
|
||||
|
||||
export type DiagnosticsPort = Readonly<{
|
||||
record(input: DiagnosticRecordInput): void;
|
||||
}>;
|
||||
@@ -0,0 +1,83 @@
|
||||
import type { SessionState } from "../auth-session-port.ts";
|
||||
import type { RuntimeCapabilitySnapshot } from "../runtime-capabilities-port.ts";
|
||||
import type { StoragePort } from "../storage-port.ts";
|
||||
|
||||
export type { SessionState } from "../auth-session-port.ts";
|
||||
export type { RuntimeCapabilitySnapshot };
|
||||
|
||||
/**
|
||||
* Features add their driving API through module augmentation. The application
|
||||
* owns the registry contract without importing any concrete feature.
|
||||
*/
|
||||
export interface ApplicationFeatureInputs {}
|
||||
|
||||
export type ApplicationFeatureId = Extract<
|
||||
keyof ApplicationFeatureInputs,
|
||||
string
|
||||
>;
|
||||
|
||||
export type ColorSchemePreference = "system" | "light" | "dark";
|
||||
|
||||
export type RenderFailureReport = Readonly<{
|
||||
routeId: string;
|
||||
buildId: string;
|
||||
boundaryName: "route" | "feature";
|
||||
}>;
|
||||
|
||||
export type RouteChangedReport = Readonly<{
|
||||
routeId: string;
|
||||
buildId: string;
|
||||
}>;
|
||||
|
||||
export type ReleaseSummary = Readonly<{
|
||||
buildId: string;
|
||||
releaseId: string;
|
||||
configSchemaVersion: string;
|
||||
/** Legacy V1 scalar; absent once the release manifest is V2. */
|
||||
apiContractVersion?: string;
|
||||
/** §5.2 contract set identity for a V2 release manifest. */
|
||||
contractSetDigest?: string;
|
||||
}>;
|
||||
|
||||
export type ApplicationApi = Readonly<{
|
||||
session: Readonly<{
|
||||
getSnapshot(): SessionState;
|
||||
subscribe(listener: () => void): () => void;
|
||||
beginSignIn(returnTo?: string): Promise<void>;
|
||||
signOut(): Promise<void>;
|
||||
recover(): Promise<"restored" | "no-session">;
|
||||
}>;
|
||||
preferences: Readonly<{
|
||||
getColorScheme(): ColorSchemePreference;
|
||||
setColorScheme(
|
||||
preference: ColorSchemePreference,
|
||||
): ReturnType<StoragePort["write"]>;
|
||||
}>;
|
||||
diagnostics: Readonly<{
|
||||
reportRenderFailure(report: RenderFailureReport): void;
|
||||
reportRouteChanged(report: RouteChangedReport): void;
|
||||
}>;
|
||||
runtime: Readonly<{
|
||||
getReleaseSummary(): Promise<ReleaseSummary>;
|
||||
/**
|
||||
* §3.5. The static selection reduced by the runtime overrides. Presentation
|
||||
* reads capability state here instead of importing the composition root.
|
||||
*/
|
||||
getCapabilitySnapshot(): RuntimeCapabilitySnapshot;
|
||||
}>;
|
||||
recovery: Readonly<{
|
||||
recoverChunk(input: Readonly<{
|
||||
chunkId: string;
|
||||
failureKind: "CHUNK_LOAD_FAILURE" | "DEPLOY_MISMATCH";
|
||||
}>): Promise<
|
||||
| Readonly<{ action: "reload-once"; releasePair: string }>
|
||||
| Readonly<{ action: "support"; reason: string }>
|
||||
>;
|
||||
}>;
|
||||
features: Readonly<{
|
||||
has(featureId: string): featureId is ApplicationFeatureId;
|
||||
get<FeatureId extends ApplicationFeatureId>(
|
||||
featureId: FeatureId,
|
||||
): ApplicationFeatureInputs[FeatureId];
|
||||
}>;
|
||||
}>;
|
||||
@@ -0,0 +1,9 @@
|
||||
export type {
|
||||
ApplicationApi,
|
||||
ApplicationFeatureId,
|
||||
ApplicationFeatureInputs,
|
||||
ColorSchemePreference,
|
||||
ReleaseSummary,
|
||||
RenderFailureReport,
|
||||
SessionState,
|
||||
} from "./application-api.ts";
|
||||
@@ -0,0 +1,11 @@
|
||||
import type { MutationIntent } from "../../contracts/mutation-intent.ts";
|
||||
|
||||
export type MutationIntentFactoryInput = Readonly<{
|
||||
operationId: string;
|
||||
canonicalInputIdentity: string;
|
||||
requiresIdempotencyKey: boolean;
|
||||
}>;
|
||||
|
||||
export type MutationIntentFactory = Readonly<{
|
||||
create(input: MutationIntentFactoryInput): MutationIntent;
|
||||
}>;
|
||||
@@ -0,0 +1,23 @@
|
||||
import type { AuthSessionPort } from "../auth-session-port.ts";
|
||||
import type { ReleaseInfoPort } from "../release-info-port.ts";
|
||||
import type { RuntimeCapabilitiesPort } from "../runtime-capabilities-port.ts";
|
||||
import type { StoragePort } from "../storage-port.ts";
|
||||
import type { TelemetryPort } from "../telemetry-port.ts";
|
||||
import type { DiagnosticsPort } from "../diagnostics-port.ts";
|
||||
|
||||
/**
|
||||
* Capabilities required by application use cases. Implementations live in
|
||||
* outbound adapters and are selected only by bootstrap.
|
||||
*/
|
||||
export type ApplicationOutputPorts = Readonly<{
|
||||
session: Pick<
|
||||
AuthSessionPort,
|
||||
"getState" | "subscribe" | "beginSignIn" | "signOut" | "recover"
|
||||
>;
|
||||
preferences: StoragePort;
|
||||
diagnostics: DiagnosticsPort;
|
||||
telemetry: TelemetryPort;
|
||||
releaseInfo: ReleaseInfoPort;
|
||||
runtimeCapabilities: RuntimeCapabilitiesPort;
|
||||
navigation: Readonly<{ reload(): void }>;
|
||||
}>;
|
||||
@@ -0,0 +1,19 @@
|
||||
export type { ApplicationOutputPorts } from "./application-output-ports.ts";
|
||||
export type {
|
||||
AuthSessionPort,
|
||||
CredentialAttacher,
|
||||
SessionGateway,
|
||||
} from "../auth-session-port.ts";
|
||||
export type { ClockPort } from "../clock-port.ts";
|
||||
export type { QueryCachePort } from "../query-cache-port.ts";
|
||||
export type { ReleaseInfoPort } from "../release-info-port.ts";
|
||||
export type { StoragePort } from "../storage-port.ts";
|
||||
export type { TelemetryPort } from "../telemetry-port.ts";
|
||||
export type { DiagnosticsPort } from "../diagnostics-port.ts";
|
||||
export type { WebPushControlPort } from "./web-push-control.ts";
|
||||
export type {
|
||||
BrowserRpcCallContext,
|
||||
BrowserRpcGenerationFence,
|
||||
BrowserRpcServerStreamPort,
|
||||
BrowserRpcUnaryPort,
|
||||
} from "../browser-rpc/index.ts";
|
||||
@@ -0,0 +1,31 @@
|
||||
import type {
|
||||
PushAuthoritySnapshot,
|
||||
WebPushReadiness,
|
||||
WebPushResult,
|
||||
} from "../../../contracts/web-push.ts";
|
||||
|
||||
export interface WebPushControlPort {
|
||||
inspect(input: Readonly<{
|
||||
authority: PushAuthoritySnapshot;
|
||||
signal?: AbortSignal;
|
||||
}>): Promise<WebPushResult<WebPushReadiness>>;
|
||||
|
||||
enable(input: Readonly<{
|
||||
authority: PushAuthoritySnapshot;
|
||||
signal: AbortSignal;
|
||||
}>): Promise<WebPushResult<WebPushReadiness>>;
|
||||
|
||||
reconcile(input: Readonly<{
|
||||
authority: PushAuthoritySnapshot;
|
||||
signal?: AbortSignal;
|
||||
}>): Promise<WebPushResult<WebPushReadiness>>;
|
||||
|
||||
revoke(input: Readonly<{
|
||||
previousAuthority: PushAuthoritySnapshot;
|
||||
nextAuthority: PushAuthoritySnapshot;
|
||||
signal?: AbortSignal;
|
||||
}>): Promise<WebPushResult<WebPushReadiness>>;
|
||||
|
||||
dispose(): void;
|
||||
}
|
||||
|
||||
@@ -0,0 +1,15 @@
|
||||
import type { ApiFailure } from "../../contracts/errors.ts";
|
||||
|
||||
export type QueryCacheReadResult =
|
||||
| Readonly<{ ok: true; value: unknown }>
|
||||
| Readonly<{ ok: false; error: ApiFailure }>;
|
||||
|
||||
export type QueryCacheWriteResult =
|
||||
| Readonly<{ ok: true }>
|
||||
| Readonly<{ ok: false; error: ApiFailure }>;
|
||||
|
||||
export type QueryCachePort = Readonly<{
|
||||
read(key: readonly unknown[]): QueryCacheReadResult;
|
||||
write(key: readonly unknown[], value: unknown): QueryCacheWriteResult;
|
||||
invalidate(namespace: readonly unknown[]): Promise<QueryCacheWriteResult>;
|
||||
}>;
|
||||
@@ -0,0 +1,202 @@
|
||||
import type {
|
||||
EventTypeId,
|
||||
ExternalEventEffectProfileId,
|
||||
StreamRegistrationId,
|
||||
} from "../../../contracts/realtime-streams.ts";
|
||||
import type {
|
||||
RealtimeResumeState,
|
||||
SnapshotCheckpoint,
|
||||
} from "../../../contracts/realtime-events.ts";
|
||||
import type {
|
||||
RealtimeFailureKind,
|
||||
RealtimeResult,
|
||||
} from "./shared.ts";
|
||||
|
||||
declare const realtimeRecoveryCheckpointBrand: unique symbol;
|
||||
|
||||
/**
|
||||
* An in-memory, one-generation recovery lease. Callers must retain the exact
|
||||
* object returned by the coordinator; reconstructing an equal checkpoint does
|
||||
* not authorize a transport-barrier commit.
|
||||
*/
|
||||
export type RealtimeRecoveryCheckpoint = RealtimeResumeState &
|
||||
Readonly<{ [realtimeRecoveryCheckpointBrand]: true }>;
|
||||
|
||||
export type RealtimeScopeSnapshot = Readonly<{
|
||||
generation: number;
|
||||
/**
|
||||
* Session/BFF-issued opaque binding. This is not a cache fingerprint or an
|
||||
* authorization credential and must never be projected into diagnostics.
|
||||
*/
|
||||
scopeBinding: string;
|
||||
isCurrent(): boolean;
|
||||
}>;
|
||||
|
||||
export type ExternalRealtimeEventContext = Readonly<{
|
||||
streamId: StreamRegistrationId;
|
||||
eventType: EventTypeId;
|
||||
occurredAt: string;
|
||||
scopeGeneration: number;
|
||||
/**
|
||||
* Per-callback commit authority. The effect owner must check this immediately
|
||||
* before its final local commit. It becomes permanently false when the
|
||||
* callback settles, even if the captured scope itself is still current.
|
||||
*/
|
||||
isCurrent(): boolean;
|
||||
}>;
|
||||
|
||||
export type RealtimeEventEffectAuthority = Readonly<{
|
||||
/**
|
||||
* Resolves a registry-owned profile to a feature-owned application input and
|
||||
* commits its local effect. Success means the complete local effect has
|
||||
* committed; only then may the coordinator advance its checkpoint.
|
||||
*/
|
||||
apply(
|
||||
effectProfileId: ExternalEventEffectProfileId,
|
||||
event: unknown,
|
||||
context: ExternalRealtimeEventContext,
|
||||
signal: AbortSignal,
|
||||
): Promise<RealtimeResult<void>>;
|
||||
}>;
|
||||
|
||||
export type RealtimeRecoveryReason =
|
||||
| "INITIALIZE"
|
||||
| "STREAM_EPOCH_CHANGED"
|
||||
| "SEQUENCE_GAP"
|
||||
| "CURSOR_EXPIRED"
|
||||
| "QUEUE_OVERFLOW"
|
||||
| "DEDUPE_OVERFLOW"
|
||||
| "EVENT_CONFLICT"
|
||||
| "MAPPING_CONTRACT_VIOLATION"
|
||||
| "APPLY_FAILED"
|
||||
| "SCOPE_PROTOCOL_VIOLATION";
|
||||
|
||||
export type RealtimeRecoveryRequest = Readonly<{
|
||||
streamId: StreamRegistrationId;
|
||||
reason: RealtimeRecoveryReason;
|
||||
scopeGeneration: number;
|
||||
signal: AbortSignal;
|
||||
/**
|
||||
* Per-recovery commit authority. Snapshot/rebuild projection owners must
|
||||
* check this immediately before commit. It becomes permanently false when
|
||||
* `recover` settles.
|
||||
*/
|
||||
isCurrent(): boolean;
|
||||
}>;
|
||||
|
||||
export type RealtimeRecoveryCommit =
|
||||
| Readonly<{
|
||||
kind: "SNAPSHOT_RESET";
|
||||
checkpoint: SnapshotCheckpoint;
|
||||
}>
|
||||
| Readonly<{
|
||||
kind: "SESSION_REBUILD";
|
||||
streamEpoch: string;
|
||||
lastAppliedSequence: string;
|
||||
}>;
|
||||
|
||||
export type RealtimeRecoveryAuthority = Readonly<{
|
||||
/**
|
||||
* Success is an authoritative commit: all required projections have already
|
||||
* been applied for the returned checkpoint in the captured scope.
|
||||
*/
|
||||
recover(
|
||||
request: RealtimeRecoveryRequest,
|
||||
): Promise<RealtimeResult<RealtimeRecoveryCommit>>;
|
||||
}>;
|
||||
|
||||
export type RealtimeEventAuthority = Readonly<{
|
||||
effects: RealtimeEventEffectAuthority;
|
||||
recovery: RealtimeRecoveryAuthority;
|
||||
}>;
|
||||
|
||||
export type RealtimeAcceptDropReason =
|
||||
| "CLOSED"
|
||||
| "DUPLICATE_EVENT"
|
||||
| "RECOVERY_IN_PROGRESS"
|
||||
| "SCOPE_FENCED"
|
||||
| "STALE_EVENT";
|
||||
|
||||
export type RealtimeAcceptDisposition =
|
||||
| Readonly<{
|
||||
outcome: "APPLIED";
|
||||
resumeState: RealtimeResumeState;
|
||||
}>
|
||||
| Readonly<{
|
||||
outcome: "DROPPED";
|
||||
reason: RealtimeAcceptDropReason;
|
||||
}>
|
||||
| Readonly<{
|
||||
outcome: "RECOVERED";
|
||||
reason: RealtimeRecoveryReason;
|
||||
resumeState: RealtimeRecoveryCheckpoint;
|
||||
}>
|
||||
| Readonly<{
|
||||
outcome: "RECOVERY_BARRIER_REQUIRED";
|
||||
resumeState: RealtimeRecoveryCheckpoint;
|
||||
}>;
|
||||
|
||||
export type RealtimeTransportEventOutcome =
|
||||
| Readonly<{ kind: "CONTINUE" }>
|
||||
| Readonly<{
|
||||
kind: "RECOVERY_COMMITTED";
|
||||
streamId: StreamRegistrationId;
|
||||
checkpoint: RealtimeRecoveryCheckpoint;
|
||||
}>;
|
||||
|
||||
export const REALTIME_TRANSPORT_CONTINUE: RealtimeTransportEventOutcome =
|
||||
Object.freeze({ kind: "CONTINUE" });
|
||||
|
||||
export function realtimeTransportRecoveryCommitted(
|
||||
streamId: StreamRegistrationId,
|
||||
checkpoint: RealtimeRecoveryCheckpoint,
|
||||
): Extract<
|
||||
RealtimeTransportEventOutcome,
|
||||
{ kind: "RECOVERY_COMMITTED" }
|
||||
> {
|
||||
return Object.freeze({
|
||||
kind: "RECOVERY_COMMITTED",
|
||||
streamId,
|
||||
checkpoint,
|
||||
});
|
||||
}
|
||||
|
||||
export type RealtimeStreamFreshness =
|
||||
| "UNKNOWN"
|
||||
| "CURRENT"
|
||||
| "STALE"
|
||||
| "RESYNCING";
|
||||
|
||||
export type RealtimeStreamInspection = Readonly<{
|
||||
freshness: RealtimeStreamFreshness;
|
||||
queuedEvents: number;
|
||||
queuedBytes: number;
|
||||
dedupeEntries: number;
|
||||
closed: boolean;
|
||||
hasResumeState: boolean;
|
||||
awaitingTransportBarrier: boolean;
|
||||
}>;
|
||||
|
||||
export type RealtimeObservationOutcome =
|
||||
| "ACCEPTED"
|
||||
| "APPLIED"
|
||||
| "DROPPED"
|
||||
| "FAILED"
|
||||
| "RECOVERED";
|
||||
|
||||
/**
|
||||
* Safe low-cardinality observation. It intentionally has no event identifier,
|
||||
* sequence, payload, cursor, epoch, scope binding or native error.
|
||||
*/
|
||||
export type RealtimeObservation = Readonly<{
|
||||
operation: "RECEIVE" | "APPLY" | "RECOVER" | "CLOSE";
|
||||
outcome: RealtimeObservationOutcome;
|
||||
streamId?: StreamRegistrationId;
|
||||
eventType?: EventTypeId;
|
||||
reason?: RealtimeFailureKind | RealtimeRecoveryReason;
|
||||
queueSizeBucket?: "0" | "1-8" | "9-64" | "65-256" | "OVERFLOW";
|
||||
}>;
|
||||
|
||||
export type RealtimeEventObservationSink = (
|
||||
observation: RealtimeObservation,
|
||||
) => void;
|
||||
@@ -0,0 +1,31 @@
|
||||
export type {
|
||||
ExternalRealtimeEventContext,
|
||||
RealtimeAcceptDisposition,
|
||||
RealtimeAcceptDropReason,
|
||||
RealtimeEventAuthority,
|
||||
RealtimeEventEffectAuthority,
|
||||
RealtimeEventObservationSink,
|
||||
RealtimeObservation,
|
||||
RealtimeObservationOutcome,
|
||||
RealtimeRecoveryCheckpoint,
|
||||
RealtimeRecoveryAuthority,
|
||||
RealtimeRecoveryCommit,
|
||||
RealtimeRecoveryReason,
|
||||
RealtimeRecoveryRequest,
|
||||
RealtimeScopeSnapshot,
|
||||
RealtimeStreamFreshness,
|
||||
RealtimeStreamInspection,
|
||||
RealtimeTransportEventOutcome,
|
||||
} from "./event-authority.ts";
|
||||
export {
|
||||
REALTIME_TRANSPORT_CONTINUE,
|
||||
realtimeTransportRecoveryCommitted,
|
||||
} from "./event-authority.ts";
|
||||
export {
|
||||
REALTIME_FAILURE_KINDS,
|
||||
REALTIME_OPERATIONS,
|
||||
type RealtimeFailure,
|
||||
type RealtimeFailureKind,
|
||||
type RealtimeOperation,
|
||||
type RealtimeResult,
|
||||
} from "./shared.ts";
|
||||
@@ -0,0 +1,67 @@
|
||||
export const REALTIME_OPERATIONS = Object.freeze([
|
||||
"REGISTRY",
|
||||
"DECODE",
|
||||
"CONNECT",
|
||||
"SUBSCRIBE",
|
||||
"RECEIVE",
|
||||
"SEND",
|
||||
"APPLY",
|
||||
"RECOVER",
|
||||
"POLL",
|
||||
"PUSH_REGISTER",
|
||||
"PUSH_REVOKE",
|
||||
"CLOSE",
|
||||
] as const);
|
||||
|
||||
export type RealtimeOperation = (typeof REALTIME_OPERATIONS)[number];
|
||||
|
||||
export const REALTIME_FAILURE_KINDS = Object.freeze([
|
||||
"ABORTED",
|
||||
"UNSUPPORTED",
|
||||
"OFFLINE",
|
||||
"CONNECT_TIMEOUT",
|
||||
"IDLE_TIMEOUT",
|
||||
"AUTH_REQUIRED",
|
||||
"FORBIDDEN",
|
||||
"RATE_LIMITED",
|
||||
"PROVIDER_UNAVAILABLE",
|
||||
"PROTOCOL_MISMATCH",
|
||||
"MALFORMED_EVENT",
|
||||
"MAPPING_CONTRACT_VIOLATION",
|
||||
"EVENT_CONFLICT",
|
||||
"EVENT_TOO_LARGE",
|
||||
"DUPLICATE_EVENT",
|
||||
"STALE_EVENT",
|
||||
"SEQUENCE_GAP",
|
||||
"CURSOR_EXPIRED",
|
||||
"QUEUE_OVERFLOW",
|
||||
"APPLY_FAILED",
|
||||
"POLL_BUDGET_EXHAUSTED",
|
||||
"PUSH_PERMISSION_DENIED",
|
||||
"PUSH_SUBSCRIPTION_STALE",
|
||||
"NOTIFICATION_REJECTED",
|
||||
"SCOPE_FENCED",
|
||||
"SCOPE_PROTOCOL_VIOLATION",
|
||||
"CLOSED",
|
||||
] as const);
|
||||
|
||||
export type RealtimeFailureKind =
|
||||
(typeof REALTIME_FAILURE_KINDS)[number];
|
||||
|
||||
/**
|
||||
* Failure projected across realtime adapter boundaries.
|
||||
*
|
||||
* Native exceptions, frames, payloads, cursors, endpoints and scope bindings
|
||||
* are deliberately absent. Adapters may observe those values locally while
|
||||
* classifying a failure, but cannot expose them through this result.
|
||||
*/
|
||||
export type RealtimeFailure = Readonly<{
|
||||
kind: RealtimeFailureKind;
|
||||
operation: RealtimeOperation;
|
||||
retryable: boolean;
|
||||
}>;
|
||||
|
||||
export type RealtimeResult<Value> =
|
||||
| Readonly<{ ok: true; value: Value }>
|
||||
| Readonly<{ ok: false; error: RealtimeFailure }>;
|
||||
|
||||
@@ -0,0 +1,36 @@
|
||||
export type ReleaseInfo = Readonly<{
|
||||
schemaVersion?: number;
|
||||
appVersion?: string;
|
||||
buildId: string;
|
||||
commitSha?: string;
|
||||
configSchemaVersion: string;
|
||||
/**
|
||||
* §5.1. Legacy scalar, present only while a V1 release manifest is still
|
||||
* accepted. A V2 manifest expresses contract identity through
|
||||
* {@link ReleaseInfo.contractSetDigest}.
|
||||
*/
|
||||
apiContractVersion?: string;
|
||||
contractSetDigest?: string;
|
||||
assetManifestHash: string;
|
||||
releaseId: string;
|
||||
builtAt?: string;
|
||||
routeChunks: Readonly<Record<string, string>>;
|
||||
}>;
|
||||
|
||||
export type ActiveReleaseInfo = Readonly<
|
||||
Pick<
|
||||
ReleaseInfo,
|
||||
| "buildId"
|
||||
| "configSchemaVersion"
|
||||
| "apiContractVersion"
|
||||
| "contractSetDigest"
|
||||
| "assetManifestHash"
|
||||
| "releaseId"
|
||||
| "routeChunks"
|
||||
>
|
||||
>;
|
||||
|
||||
export type ReleaseInfoPort = Readonly<{
|
||||
getCurrent(): Promise<ReleaseInfo>;
|
||||
refresh(): Promise<ActiveReleaseInfo>;
|
||||
}>;
|
||||
@@ -0,0 +1,12 @@
|
||||
import type { RuntimeCapabilitySnapshot } from "../../contracts/runtime-capabilities.ts";
|
||||
|
||||
export type { RuntimeCapabilitySnapshot };
|
||||
|
||||
/**
|
||||
* §3.5. The application reads capability state; it never resolves it. Only the
|
||||
* composition root knows the runtime overrides, so the snapshot arrives here
|
||||
* already reduced to counts and cannot be used to reach a runtime object.
|
||||
*/
|
||||
export type RuntimeCapabilitiesPort = Readonly<{
|
||||
getSnapshot(): RuntimeCapabilitySnapshot;
|
||||
}>;
|
||||
@@ -0,0 +1,15 @@
|
||||
import type { ApiFailure } from "../../contracts/errors.ts";
|
||||
|
||||
export type StorageReadResult =
|
||||
| Readonly<{ ok: true; value: unknown }>
|
||||
| Readonly<{ ok: false; error: ApiFailure }>;
|
||||
|
||||
export type StorageMutationResult =
|
||||
| Readonly<{ ok: true }>
|
||||
| Readonly<{ ok: false; error: ApiFailure; fallback?: string }>;
|
||||
|
||||
export type StoragePort = Readonly<{
|
||||
read(logicalName: string): StorageReadResult;
|
||||
write(logicalName: string, value: unknown): StorageMutationResult;
|
||||
remove(logicalName: string): StorageMutationResult;
|
||||
}>;
|
||||
@@ -0,0 +1,10 @@
|
||||
import type { TelemetryEventName } from "../../contracts/telemetry.ts";
|
||||
|
||||
export type { TelemetryEventName };
|
||||
|
||||
export type TelemetryPort = Readonly<{
|
||||
emit(
|
||||
eventName: TelemetryEventName,
|
||||
attributes: Record<string, unknown>,
|
||||
): void;
|
||||
}>;
|
||||
Reference in New Issue
Block a user