// oxlint-disable no-unsafe-member-access // oxlint-disable no-undef // oxlint-disable no-typeof-undefined import type { StringAutoCompletable } from "#Source/type/index.ts" import type { Use } from "./basic.ts" import { useFactory } from "./basic.ts" /** * @description Define built-in and custom runtime keys. */ export type Runtime = | StringAutoCompletable | "browser" | "nodejs" | "deno" | "bun" | "web-worker" | "service-worker" | "unknown" interface RuntimeRegistryItem { runtime: Runtime detect: () => boolean // oxlint-disable-next-line no-explicit-any getGlobalContext: () => any // oxlint-disable-next-line no-explicit-any use: Use } const internalRuntimeRegistry = new Map() /** * @description Check whether a runtime is registered. */ export const isRuntimeRegistered = (runtime: Runtime): boolean => { return internalRuntimeRegistry.has(runtime) } /** * @description Register a runtime detector and context provider. */ export const registerRuntime = (item: RuntimeRegistryItem): void => { internalRuntimeRegistry.set(item.runtime, item) } /** * @description Unregister a runtime by key. */ export const unregisterRuntime = (name: Runtime): void => { internalRuntimeRegistry.delete(name) } /** * @description List all registered runtime keys. */ export const listRuntimes = (): Runtime[] => { return Array.from(internalRuntimeRegistry.keys()) } /** * @description Check if the environment is a browser. */ export const isBrowser = (): boolean => { return typeof window !== "undefined" && typeof window.document !== "undefined" } /** * @description Describe runtime context values available in browser. */ export interface RuntimeContextBrowser { global: Window & typeof globalThis self: Window & typeof globalThis window: Window & typeof globalThis globalThis: typeof globalThis importMeta: ImportMeta } /** * @description Return browser runtime context values. */ export const getRuntimeContextBrowser = (): RuntimeContextBrowser => { return { global: window, self, window, globalThis, importMeta: import.meta } } /** * @description Execute logic with browser runtime context when available. */ export const useBrowser: Use = useFactory( getRuntimeContextBrowser, isBrowser, ) registerRuntime({ runtime: "browser", detect: isBrowser, getGlobalContext: getRuntimeContextBrowser, use: useBrowser, }) /** * @description Check if the environment is Node.js. */ export const isNodejs = (): boolean => { return ( typeof process !== "undefined" && process.versions !== null && process.versions.node !== null ) } /** * @description Describe runtime context values available in Node.js. */ export interface RuntimeContextNodejs { global: typeof globalThis globalThis: typeof globalThis } /** * @description Return Node.js runtime context values. */ export const getRuntimeContextNodejs = (): RuntimeContextNodejs => { return { global: globalThis, globalThis } } /** * @description Execute logic with Node.js runtime context when available. */ export const useNodejs: Use = useFactory(getRuntimeContextNodejs, isNodejs) registerRuntime({ runtime: "nodejs", detect: isNodejs, getGlobalContext: getRuntimeContextNodejs, use: useNodejs, }) /** * @description Check if the environment is Deno. */ export const isDeno = (): boolean => { return ( // @ts-expect-error Deno is not defined in some environments typeof Deno !== "undefined" && // @ts-expect-error Deno is not defined in some environments typeof Deno.version !== "undefined" && // @ts-expect-error Deno is not defined in some environments typeof Deno.version.deno !== "undefined" ) } /** * @description Describe runtime context values available in Deno. */ export interface RuntimeContextDeno { global: typeof globalThis globalThis: typeof globalThis } /** * @description Return Deno runtime context values. */ export const getRuntimeContextDeno = (): RuntimeContextDeno => { return { global: globalThis, globalThis } } /** * @description Execute logic with Deno runtime context when available. */ export const useDeno: Use = useFactory(getRuntimeContextDeno, isDeno) registerRuntime({ runtime: "deno", detect: isDeno, getGlobalContext: getRuntimeContextDeno, use: useDeno, }) type OptionalBunGlobal = typeof globalThis & { Bun?: typeof Bun | undefined } const getOptionalBunGlobal = (): typeof Bun | undefined => { const preparedGlobalThis = globalThis as OptionalBunGlobal return preparedGlobalThis.Bun } const getRequiredBunGlobal = (): typeof Bun => { const bunGlobal = getOptionalBunGlobal() if (bunGlobal === undefined) { throw new Error("Bun runtime is not available in the current environment.") } return bunGlobal } /** * @description Check if the environment is Bun. */ export const isBun = (): boolean => { const bunGlobal = getOptionalBunGlobal() return bunGlobal !== undefined && typeof bunGlobal.version !== "undefined" } /** * @description Describe runtime context values available in Bun. */ export interface RuntimeContextBun { global: typeof globalThis Bun: typeof Bun globalThis: typeof globalThis importMeta: ImportMeta } /** * @description Return Bun runtime context values. */ export const getRuntimeContextBun = (): RuntimeContextBun => { return { global: globalThis, Bun: getRequiredBunGlobal(), globalThis, importMeta: import.meta } } /** * @description Execute logic with Bun runtime context when available. */ export const useBun: Use = useFactory(getRuntimeContextBun, isBun) registerRuntime({ runtime: "bun", detect: isBun, getGlobalContext: getRuntimeContextBun, use: useBun, }) /** * @description Check if the environment is a Web Worker. */ export const isWebWorker = (): boolean => { return ( typeof self !== "undefined" && typeof WorkerGlobalScope !== "undefined" && self instanceof WorkerGlobalScope && (typeof ServiceWorkerGlobalScope === "undefined" || !(self instanceof ServiceWorkerGlobalScope)) ) } /** * @description Describe runtime context values available in Web Worker. */ export interface RuntimeContextWebWorker { global: typeof globalThis self: WorkerGlobalScope & typeof globalThis globalThis: typeof globalThis } /** * @description Return Web Worker runtime context values. */ export const getRuntimeContextWebWorker = (): RuntimeContextWebWorker => { // @ts-expect-error - self is not defined in some environments return { global: self, self, globalThis } } /** * @description Execute logic with Web Worker runtime context when available. */ export const useWebWorker: Use = useFactory( getRuntimeContextWebWorker, isWebWorker, ) registerRuntime({ runtime: "web-worker", detect: isWebWorker, getGlobalContext: getRuntimeContextWebWorker, use: useWebWorker, }) /** * @description Check if the environment is a Service Worker. */ export const isServiceWorker = (): boolean => { return ( typeof self !== "undefined" && typeof ServiceWorkerGlobalScope !== "undefined" && self instanceof ServiceWorkerGlobalScope ) } /** * @description Describe runtime context values available in Service Worker. */ export interface RuntimeContextServiceWorker { global: typeof globalThis self: ServiceWorkerGlobalScope & typeof globalThis globalThis: typeof globalThis } /** * @description Return Service Worker runtime context values. */ export const getServiceWorkerContext = (): RuntimeContextServiceWorker => { // @ts-expect-error - self is not defined in some environments return { global: self, self, globalThis } } /** * @description Execute logic with Service Worker runtime context when available. */ export const useServiceWorker: Use = useFactory( getServiceWorkerContext, isServiceWorker, ) registerRuntime({ runtime: "service-worker", detect: isServiceWorker, getGlobalContext: getServiceWorkerContext, use: useServiceWorker, }) /** * @description Check if the environment is unknown. */ export const isUnknown = (): boolean => { return getRuntimeRegistryItem().runtime === "unknown" } /** * @description Describe runtime context values for unknown runtime. */ export interface RuntimeContextUnknown { global: unknown } /** * @description Return unknown runtime context values. */ export const getRuntimeContextUnknown = (): RuntimeContextUnknown => { return { global: undefined } } /** * @description Execute logic with unknown runtime context when available. */ export const useUnknown: Use = useFactory( getRuntimeContextUnknown, isUnknown, ) registerRuntime({ runtime: "unknown", detect: isUnknown, getGlobalContext: getRuntimeContextUnknown, use: useUnknown, }) /** * @description Get the current runtime environment. */ export const getRuntimeRegistryItem = (): RuntimeRegistryItem => { // 如果不是其它任何 Runtime,则是 unknown // 这样写可以兼容用户自行注册的 Runtime for (const [name, item] of internalRuntimeRegistry) { if (name === "unknown") { continue } if (item.detect() === true) { return item } } return internalRuntimeRegistry.get("unknown")! } /** * @description Get the key of the current runtime environment. */ export const getRuntime = (): RuntimeRegistryItem["runtime"] => { return getRuntimeRegistryItem().runtime } /** * @description Define detection results for registered runtimes. */ export type RuntimeFlags = Record /** * @description Define a function type that resolves all runtime flags. */ export type GetRuntimeFlags = () => RuntimeFlags /** * @description Get the runtime flags for all registered runtimes. */ export const getRuntimeFlags: GetRuntimeFlags = (): RuntimeFlags => { const flags = {} as RuntimeFlags for (const [name, item] of internalRuntimeRegistry) { flags[name] = item.detect() === true } return flags } /** * @description Check if the current runtime satisfies the specified runtime. */ export const isSatisfiesRuntime = (runtime: Runtime): boolean => { const runtimeRegistryItem = internalRuntimeRegistry.get(runtime) if (runtimeRegistryItem === undefined) { throw new Error(`Runtime "${runtime}" is not registered.`) } return runtimeRegistryItem.detect() === true } /** * @description Check if the current runtime satisfies the specified runtimes condition. */ export const isSatisfiesRuntimes = (condition: Partial): boolean => { let result: boolean = true Object.keys(condition).forEach((runtime) => { const runtimeRegistryItem = internalRuntimeRegistry.get(runtime) if (runtimeRegistryItem === undefined) { throw new Error(`Runtime "${runtime}" is not registered.`) } if (runtimeRegistryItem.detect() !== condition[runtime]) { result = false } }) return result } /** * @description Map runtime keys to their typed context payloads. */ export interface RuntimeContexts { browser: RuntimeContextBrowser nodejs: RuntimeContextNodejs deno: RuntimeContextDeno bun: RuntimeContextBun "web-worker": RuntimeContextWebWorker "service-worker": RuntimeContextServiceWorker unknown: RuntimeContextUnknown } /** * @description Define handler options for runtime-conditional execution. */ export type UseRuntimesOptions = { // 这里用 unknown 更合理,但 unknown 会有类型问题无法解决,暂时不得不用 any // 若有解决方案请替换回 unknown // oxlint-disable-next-line no-explicit-any [K in Runtime]?: (context: K extends keyof RuntimeContexts ? RuntimeContexts[K] : any) => R } & { default?: () => R } /** * @description Execute a runtime-specific handler with fallback support. */ export const useRuntimes = (options: UseRuntimesOptions): R => { const runtimeRegisterItem = getRuntimeRegistryItem() const runtime = runtimeRegisterItem.runtime if (options[runtime] !== undefined) { return options[runtime](runtimeRegisterItem.getGlobalContext()) } if (options.default !== undefined) { return options.default() } else { throw new Error( `Neither runtime-specific nor default handler provided for runtime: ${String(runtime)}`, ) } }