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,249 @@
import {
isCacheInvalidationOpaqueIdentifier,
type CacheInvalidationTopicDefinition,
} from "../../contracts/cache-invalidation.ts";
import {
createBrowserCrossContextInvalidation,
type BroadcastChannelFacade,
type BroadcastMessageListener,
type BrowserCrossContextInvalidation,
type CrossContextInvalidationObservation,
type StorageEventTargetFacade,
type StoragePulseFacade,
type StoragePulseListener,
} from "./browser-cross-context-invalidation.ts";
export type BrowserCrossContextHostDependencies = Readonly<{
host?: Record<string, unknown>;
cacheEpoch: string;
topics: readonly CacheInvalidationTopicDefinition[];
observe?: (observation: CrossContextInvalidationObservation) => void;
}>;
type NativeBroadcastChannel = Readonly<{
postMessage(value: unknown): void;
addEventListener(type: string, listener: (event: unknown) => void): void;
removeEventListener(
type: string,
listener: (event: unknown) => void,
): void;
close(): void;
}>;
const CHANNEL_NAME = "ca-client-cache-invalidation-v1";
const STORAGE_PULSE_KEY =
"ca-frontend:cache-invalidation:v1:pulse";
/**
* Captures native capabilities without allowing a SecurityError getter or a
* missing random source to fail application boot.
*/
export function createBrowserCrossContextInvalidationFromHost(
dependencies: BrowserCrossContextHostDependencies,
): BrowserCrossContextInvalidation | undefined {
// A zero-feature build owns no cross-context invalidation runtime. Preserve
// that property strictly: do not even probe browser capability getters.
if (dependencies.topics.length === 0) return undefined;
const host =
dependencies.host ??
(globalThis as unknown as Record<string, unknown>);
if (
!isCacheInvalidationOpaqueIdentifier(dependencies.cacheEpoch)
) {
return undefined;
}
const createOpaqueId = randomIdFactory(host);
if (!createOpaqueId) return undefined;
const sourceId = createOpaqueId("tab");
const sourceEpoch = createOpaqueId("page");
if (!sourceId || !sourceEpoch) return undefined;
return createBrowserCrossContextInvalidation({
channelName: CHANNEL_NAME,
storagePulseKey: STORAGE_PULSE_KEY,
sourceId,
sourceEpoch,
cacheEpoch: dependencies.cacheEpoch,
topics: dependencies.topics,
createEventId: () => {
const eventId = createOpaqueId("event");
if (!eventId) throw new TypeError("Secure random is unavailable.");
return eventId;
},
createBroadcastChannel: broadcastFactory(host),
storage: storageFacade(host),
storageEvents: storageEventTarget(host),
observe: dependencies.observe,
});
}
function safeGet(
target: Record<string, unknown>,
property: string,
): unknown {
try {
return Reflect.get(target, property);
} catch {
return undefined;
}
}
function randomIdFactory(
host: Record<string, unknown>,
): ((prefix: string) => string | null) | undefined {
const cryptoCandidate = safeGet(host, "crypto");
if (!cryptoCandidate || typeof cryptoCandidate !== "object") {
return undefined;
}
const randomUuid = safeGet(
cryptoCandidate as Record<string, unknown>,
"randomUUID",
);
if (typeof randomUuid !== "function") return undefined;
return (prefix) => {
try {
const value = Reflect.apply(randomUuid, cryptoCandidate, []);
if (typeof value !== "string") return null;
const candidate = `${prefix}.${value}`;
return isCacheInvalidationOpaqueIdentifier(candidate)
? candidate
: null;
} catch {
return null;
}
};
}
function broadcastFactory(
host: Record<string, unknown>,
):
| ((name: string) => BroadcastChannelFacade)
| undefined {
const Constructor = safeGet(host, "BroadcastChannel");
if (typeof Constructor !== "function") return undefined;
return (name) => {
const candidate = Reflect.construct(Constructor, [name]) as unknown;
if (!isNativeBroadcastChannel(candidate)) {
throw new TypeError("BroadcastChannel is incompatible.");
}
const listenerBindings = new Map<
BroadcastMessageListener,
(event: unknown) => void
>();
return Object.freeze({
postMessage(value: unknown) {
candidate.postMessage(value);
},
addEventListener(
_type: "message",
listener: BroadcastMessageListener,
) {
const bound = (event: unknown) => {
listener({
data:
event && typeof event === "object"
? safeGet(
event as Record<string, unknown>,
"data",
)
: undefined,
});
};
listenerBindings.set(listener, bound);
candidate.addEventListener("message", bound);
},
removeEventListener(
_type: "message",
listener: BroadcastMessageListener,
) {
const bound = listenerBindings.get(listener);
if (!bound) return;
listenerBindings.delete(listener);
candidate.removeEventListener("message", bound);
},
close() {
listenerBindings.clear();
candidate.close();
},
});
};
}
function isNativeBroadcastChannel(
value: unknown,
): value is NativeBroadcastChannel {
if (!value || typeof value !== "object") return false;
const candidate = value as Record<string, unknown>;
return ["postMessage", "addEventListener", "removeEventListener", "close"].every(
(method) => typeof safeGet(candidate, method) === "function",
);
}
function storageFacade(
host: Record<string, unknown>,
): StoragePulseFacade | undefined {
const candidate = safeGet(host, "localStorage");
if (!candidate || typeof candidate !== "object") return undefined;
const record = candidate as Record<string, unknown>;
const setItem = safeGet(record, "setItem");
const removeItem = safeGet(record, "removeItem");
if (typeof setItem !== "function" || typeof removeItem !== "function") {
return undefined;
}
return Object.freeze({
setItem(key, value) {
Reflect.apply(setItem, candidate, [key, value]);
},
removeItem(key) {
Reflect.apply(removeItem, candidate, [key]);
},
});
}
function storageEventTarget(
host: Record<string, unknown>,
): StorageEventTargetFacade | undefined {
const addEventListener = safeGet(host, "addEventListener");
const removeEventListener = safeGet(host, "removeEventListener");
if (
typeof addEventListener !== "function" ||
typeof removeEventListener !== "function"
) {
return undefined;
}
const bindings = new Map<
StoragePulseListener,
(event: unknown) => void
>();
return Object.freeze({
addEventListener(_type: "storage", listener: StoragePulseListener) {
const bound = (event: unknown) => {
if (!event || typeof event !== "object") {
listener({ key: null, newValue: null });
return;
}
const record = event as Record<string, unknown>;
const key = safeGet(record, "key");
const newValue = safeGet(record, "newValue");
listener({
key: typeof key === "string" ? key : null,
newValue: typeof newValue === "string" ? newValue : null,
});
};
bindings.set(listener, bound);
Reflect.apply(addEventListener, host, ["storage", bound]);
},
removeEventListener(
_type: "storage",
listener: StoragePulseListener,
) {
const bound = bindings.get(listener);
if (!bound) return;
bindings.delete(listener);
Reflect.apply(removeEventListener, host, ["storage", bound]);
},
});
}