chore: initialize from frontend template 4dc033c

This commit is contained in:
DongHyeonka
2026-08-13 18:23:26 +09:00
commit 40107eec84
897 changed files with 234824 additions and 0 deletions
@@ -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" }>>>;
}
+4
View File
@@ -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];
}>;
}>;
+9
View File
@@ -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 }>;
}>;
+19
View File
@@ -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;
}
+15
View File
@@ -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;
+31
View File
@@ -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";
+67
View File
@@ -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;
}>;
+15
View File
@@ -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;
}>;
+10
View File
@@ -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;
}>;