chore: initialize from frontend template 4dc033c
This commit is contained in:
@@ -0,0 +1,236 @@
|
||||
import {
|
||||
assertValidStoragePolicy,
|
||||
isValidByteLength,
|
||||
type BrowserDataFailureCode,
|
||||
type BrowserDataOperation,
|
||||
type BrowserStoragePolicy,
|
||||
} from "../../../application/ports/browser-file-storage/shared.ts";
|
||||
import type { OpfsStorageScope } from "../../../application/ports/browser-file-storage/opfs-ports.ts";
|
||||
|
||||
export type OpfsRuntimePolicy = Readonly<{
|
||||
rootDirectoryName: string;
|
||||
mutationLockName: string;
|
||||
chunkSizeBytes: number;
|
||||
maxObjectBytes: number;
|
||||
maxChunkCount: number;
|
||||
rpcTimeoutMs: number;
|
||||
reconciliationBudgetMs: number;
|
||||
reconciliationBatchSize: number;
|
||||
orphanGracePeriodMs: number;
|
||||
orphanGcBatchSize: number;
|
||||
maxCancellationTombstones: number;
|
||||
allowAsyncWritableChunkFallback: boolean;
|
||||
isObjectIdAllowed: (objectId: string) => boolean;
|
||||
isMediaTypeAllowed: (mediaType: string) => boolean;
|
||||
}>;
|
||||
|
||||
export type OpfsSafeObservation = Readonly<{
|
||||
operation: BrowserDataOperation;
|
||||
outcome: "STARTED" | "SUCCEEDED" | "FAILED";
|
||||
failureCode?: BrowserDataFailureCode;
|
||||
byteBucket?: "0" | "1B_1MiB" | "1MiB_16MiB" | "16MiB_256MiB" | "GT_256MiB";
|
||||
transactionBucket?: "0" | "1_10" | "11_100" | "GT_100";
|
||||
}>;
|
||||
|
||||
export type OpfsSafeObserver = (observation: OpfsSafeObservation) => void;
|
||||
|
||||
const SAFE_SEGMENT = /^[a-z0-9][a-z0-9._-]{0,63}$/u;
|
||||
const OPAQUE_OBJECT_ID = /^[A-Za-z0-9_-]{8,128}$/u;
|
||||
const MEDIA_TYPE = /^[a-z0-9!#$&^_.+-]+\/[a-z0-9!#$&^_.+-]+(?:\s*;.*)?$/iu;
|
||||
|
||||
export const DEFAULT_OPFS_RUNTIME_POLICY: OpfsRuntimePolicy = Object.freeze({
|
||||
rootDirectoryName: "ca-frontend-opfs-v1",
|
||||
mutationLockName: "ca-frontend-opfs-v1:mutation",
|
||||
chunkSizeBytes: 4 * 1024 * 1024,
|
||||
maxObjectBytes: 2 * 1024 * 1024 * 1024,
|
||||
maxChunkCount: 512,
|
||||
rpcTimeoutMs: 60_000,
|
||||
reconciliationBudgetMs: 5_000,
|
||||
reconciliationBatchSize: 100,
|
||||
orphanGracePeriodMs: 24 * 60 * 60 * 1_000,
|
||||
orphanGcBatchSize: 100,
|
||||
maxCancellationTombstones: 1_024,
|
||||
allowAsyncWritableChunkFallback: true,
|
||||
isObjectIdAllowed: (objectId) => OPAQUE_OBJECT_ID.test(objectId),
|
||||
isMediaTypeAllowed: (mediaType) => MEDIA_TYPE.test(mediaType),
|
||||
});
|
||||
|
||||
export function resolveOpfsRuntimePolicy(
|
||||
policy: Partial<OpfsRuntimePolicy> = {},
|
||||
): OpfsRuntimePolicy {
|
||||
const resolved: OpfsRuntimePolicy = Object.freeze({
|
||||
...DEFAULT_OPFS_RUNTIME_POLICY,
|
||||
...policy,
|
||||
});
|
||||
assertOpfsRuntimePolicy(resolved);
|
||||
return resolved;
|
||||
}
|
||||
|
||||
export function assertOpfsRuntimePolicy(policy: OpfsRuntimePolicy): void {
|
||||
if (
|
||||
!SAFE_SEGMENT.test(policy.rootDirectoryName) ||
|
||||
policy.mutationLockName.length === 0 ||
|
||||
!Number.isSafeInteger(policy.chunkSizeBytes) ||
|
||||
policy.chunkSizeBytes < 64 * 1024 ||
|
||||
policy.chunkSizeBytes > 64 * 1024 * 1024 ||
|
||||
!isValidByteLength(policy.maxObjectBytes) ||
|
||||
policy.maxObjectBytes < policy.chunkSizeBytes ||
|
||||
!Number.isSafeInteger(policy.maxChunkCount) ||
|
||||
policy.maxChunkCount < 1 ||
|
||||
policy.maxObjectBytes > policy.chunkSizeBytes * policy.maxChunkCount ||
|
||||
!Number.isSafeInteger(policy.rpcTimeoutMs) ||
|
||||
policy.rpcTimeoutMs < 1_000 ||
|
||||
!Number.isSafeInteger(policy.reconciliationBudgetMs) ||
|
||||
policy.reconciliationBudgetMs < 1 ||
|
||||
policy.reconciliationBudgetMs > 60_000 ||
|
||||
!Number.isSafeInteger(policy.reconciliationBatchSize) ||
|
||||
policy.reconciliationBatchSize < 1 ||
|
||||
policy.reconciliationBatchSize > 1_000 ||
|
||||
!Number.isSafeInteger(policy.orphanGracePeriodMs) ||
|
||||
policy.orphanGracePeriodMs < 60_000 ||
|
||||
!Number.isSafeInteger(policy.orphanGcBatchSize) ||
|
||||
policy.orphanGcBatchSize < 1 ||
|
||||
policy.orphanGcBatchSize > 1_000 ||
|
||||
!Number.isSafeInteger(policy.maxCancellationTombstones) ||
|
||||
policy.maxCancellationTombstones < 16 ||
|
||||
policy.maxCancellationTombstones > 10_000 ||
|
||||
typeof policy.isObjectIdAllowed !== "function" ||
|
||||
typeof policy.isMediaTypeAllowed !== "function"
|
||||
) {
|
||||
throw new TypeError("OPFS runtime policy is invalid.");
|
||||
}
|
||||
}
|
||||
|
||||
export function validateObjectWriteInput(
|
||||
input: Readonly<{
|
||||
scope: OpfsStorageScope;
|
||||
objectId: string;
|
||||
expectedGeneration: number | null;
|
||||
mediaType: string;
|
||||
byteLength: number | null;
|
||||
storagePolicy: Parameters<typeof assertValidStoragePolicy>[0];
|
||||
}>,
|
||||
policy: OpfsRuntimePolicy,
|
||||
): boolean {
|
||||
try {
|
||||
assertValidStoragePolicy(input.storagePolicy);
|
||||
} catch {
|
||||
return false;
|
||||
}
|
||||
|
||||
return (
|
||||
isValidOpfsStorageScope(input.scope) &&
|
||||
input.scope.namespace === input.storagePolicy.namespace &&
|
||||
policy.isObjectIdAllowed(input.objectId) &&
|
||||
policy.isMediaTypeAllowed(input.mediaType) &&
|
||||
(input.expectedGeneration === null ||
|
||||
(Number.isSafeInteger(input.expectedGeneration) &&
|
||||
input.expectedGeneration > 0)) &&
|
||||
input.byteLength !== null &&
|
||||
isValidByteLength(input.byteLength) &&
|
||||
input.byteLength <= policy.maxObjectBytes &&
|
||||
Math.ceil(input.byteLength / policy.chunkSizeBytes) <=
|
||||
policy.maxChunkCount
|
||||
);
|
||||
}
|
||||
|
||||
export function isValidOpfsStorageScope(
|
||||
scope: OpfsStorageScope,
|
||||
): boolean {
|
||||
return (
|
||||
scope.namespace.length > 0 &&
|
||||
scope.namespace.length <= 64 &&
|
||||
OPAQUE_OBJECT_ID.test(scope.authorityToken) &&
|
||||
OPAQUE_OBJECT_ID.test(scope.namespaceToken) &&
|
||||
OPAQUE_OBJECT_ID.test(scope.partitionToken)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Captures the registry binding at composition time. Callers may own mutable
|
||||
* config objects, so no OPFS operation is allowed to retain those references.
|
||||
*/
|
||||
export function snapshotOpfsStorageScope(
|
||||
input: OpfsStorageScope,
|
||||
): OpfsStorageScope {
|
||||
try {
|
||||
const snapshot: OpfsStorageScope = Object.freeze({
|
||||
namespace: input.namespace,
|
||||
authorityToken: input.authorityToken,
|
||||
namespaceToken: input.namespaceToken,
|
||||
partitionToken: input.partitionToken,
|
||||
});
|
||||
if (!isValidOpfsStorageScope(snapshot)) throw new TypeError();
|
||||
return snapshot;
|
||||
} catch {
|
||||
throw new TypeError("OPFS storage scope is invalid.");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Deep enough for the closed BrowserStoragePolicy contract: retention is the
|
||||
* only nested value. Fields are copied explicitly so later caller mutation or
|
||||
* extension properties cannot alter the bound policy fingerprint.
|
||||
*/
|
||||
export function snapshotOpfsStoragePolicy(
|
||||
input: BrowserStoragePolicy,
|
||||
): BrowserStoragePolicy {
|
||||
try {
|
||||
const retention: BrowserStoragePolicy["retention"] =
|
||||
input.retention.kind === "TTL"
|
||||
? Object.freeze({
|
||||
kind: "TTL",
|
||||
maxAgeMs: input.retention.maxAgeMs,
|
||||
})
|
||||
: Object.freeze({ kind: input.retention.kind });
|
||||
const snapshot: BrowserStoragePolicy = Object.freeze({
|
||||
owner: input.owner,
|
||||
namespace: input.namespace,
|
||||
classification: input.classification,
|
||||
authority: input.authority,
|
||||
accountScope: input.accountScope,
|
||||
retention,
|
||||
softBudgetBytes: input.softBudgetBytes,
|
||||
hardBudgetBytes: input.hardBudgetBytes,
|
||||
evictionPriority: input.evictionPriority,
|
||||
logoutAction: input.logoutAction,
|
||||
accountDeletionAction: input.accountDeletionAction,
|
||||
pressureAction: input.pressureAction,
|
||||
unavailableFallback: input.unavailableFallback,
|
||||
});
|
||||
assertValidStoragePolicy(snapshot);
|
||||
return snapshot;
|
||||
} catch {
|
||||
throw new TypeError("OPFS storage policy is invalid.");
|
||||
}
|
||||
}
|
||||
|
||||
export function byteBucket(
|
||||
byteLength: number,
|
||||
): NonNullable<OpfsSafeObservation["byteBucket"]> {
|
||||
if (byteLength === 0) return "0";
|
||||
if (byteLength <= 1024 * 1024) return "1B_1MiB";
|
||||
if (byteLength <= 16 * 1024 * 1024) return "1MiB_16MiB";
|
||||
if (byteLength <= 256 * 1024 * 1024) return "16MiB_256MiB";
|
||||
return "GT_256MiB";
|
||||
}
|
||||
|
||||
export function transactionBucket(
|
||||
count: number,
|
||||
): NonNullable<OpfsSafeObservation["transactionBucket"]> {
|
||||
if (count === 0) return "0";
|
||||
if (count <= 10) return "1_10";
|
||||
if (count <= 100) return "11_100";
|
||||
return "GT_100";
|
||||
}
|
||||
|
||||
export function observeOpfsSafely(
|
||||
observer: OpfsSafeObserver | undefined,
|
||||
observation: OpfsSafeObservation,
|
||||
): void {
|
||||
try {
|
||||
observer?.(Object.freeze({ ...observation }));
|
||||
} catch {
|
||||
// Persistence behavior never depends on observability.
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user