commit c726cd8804ddfe5416d2b04026e456f54cc84952 Author: abearxiong Date: Sun Feb 15 19:19:19 2026 +0800 init diff --git a/.cnb.yml b/.cnb.yml new file mode 100644 index 0000000..f972dd3 --- /dev/null +++ b/.cnb.yml @@ -0,0 +1,19 @@ +# .cnb.yml +include: + - https://cnb.cool/kevisual/cnb/-/blob/main/.cnb/template.yml + +.common_env: &common_env + env: + USERNAME: root + imports: + - https://cnb.cool/kevisual/env/-/blob/main/.env.development + +$: + vscode: + - docker: + image: docker.cnb.cool/kevisual/dev-env:latest + services: + - vscode + - docker + imports: !reference [.common_env, imports] + stages: !reference [.dev_template, stages] \ No newline at end of file diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..96d2b74 --- /dev/null +++ b/.gitignore @@ -0,0 +1,2 @@ +storage +node_modules \ No newline at end of file diff --git a/.npmrc b/.npmrc new file mode 100644 index 0000000..a5aa07b --- /dev/null +++ b/.npmrc @@ -0,0 +1,2 @@ +//npm.xiongxiao.me/:_authToken=${ME_NPM_TOKEN} +//registry.npmjs.org/:_authToken=${NPM_TOKEN} diff --git a/bun.config.mjs b/bun.config.mjs new file mode 100644 index 0000000..67f6709 --- /dev/null +++ b/bun.config.mjs @@ -0,0 +1,25 @@ +// @ts-check +import { resolvePath } from '@kevisual/use-config'; +import { execSync } from 'node:child_process'; + +const entry = 'src/index.ts'; +const naming = 'app'; +const external = ['bullmq','ioredis']; +/** + * @type {import('bun').BuildConfig} + */ +await Bun.build({ + target: 'node', + format: 'esm', + entrypoints: [resolvePath(entry, { meta: import.meta })], + outdir: resolvePath('./dist', { meta: import.meta }), + naming: { + entry: `${naming}.js`, + }, + external: external, + env: 'KEVISUAL_*', +}); + +// const cmd = `dts -i src/index.ts -o app.d.ts`; +const cmd = `dts -i ${entry} -o ${naming}.d.ts`; +execSync(cmd, { stdio: 'inherit' }); diff --git a/package.json b/package.json new file mode 100644 index 0000000..d9dd621 --- /dev/null +++ b/package.json @@ -0,0 +1,35 @@ +{ + "name": "@kevisual/context", + "version": "0.0.4", + "description": "", + "main": "index.js", + "scripts": { + "dev": "bun src/dev.ts --watch", + "build": "bun bun.config.mjs", + "publish": "ev npm publish npm -p" + }, + "files": [ + "dist", + "src" + ], + "publishConfig": { + "access": "public" + }, + "keywords": [], + "author": "abearxiong (https://www.xiongxiao.me)", + "license": "MIT", + "packageManager": "pnpm@10.14.0", + "type": "module", + "exports": { + ".": { + "import": "./dist/app.js", + "types": "./dist/app.d.ts" + } + }, + "devDependencies": { + "@kevisual/load": "^0.0.6", + "@kevisual/types": "^0.0.10", + "@kevisual/use-config": "^1.0.19", + "@types/node": "^24.2.1" + } +} \ No newline at end of file diff --git a/readme.md b/readme.md new file mode 100644 index 0000000..5f41f82 --- /dev/null +++ b/readme.md @@ -0,0 +1,40 @@ +# 环境变量 context + +上下文 的node或则浏览器当中,如果global中不存在context,则赋值到global,如果存在,则获取对应的。 + +```ts +import { useContextKey } from '@kevisual/context'; + +const v = useContextKey('a', () => '123'); // 如果是第一次初始化,返回123,否则返回之前初始化的值 +``` + + + +### types +```ts +type GlobalContext = { + name?: string; + [key: string]: any; +} & T; +declare const useEnv: (initEnv?: GlobalContext, initKey?: string, isOverwrite?: boolean) => Required>; +type InitResult = T | Promise | null; +type InitFn = () => T | Promise; +type Init = T | InitFn | null; +type AsyncResult = ASYNC extends true ? Promise : T; +type SimpleObject = Record; +declare const useEnvKey: (key: string, init?: Init, initKey?: string) => InitResult; +declare const usePageEnv: (init?: Init, initKey?: string) => any; +declare const useEnvKeyNew: (key: string, initKey?: string, opts?: { + getNew?: boolean; + init?: Init; +}) => any; +declare const useContext: (initContext?: GlobalContext, isOverwrite?: boolean) => Required>; +declare const useContextKey: (key: string, init?: Init, isNew?: boolean) => AsyncResult; +declare const usePageContext: (init?: Init) => any; +declare const useConfig: (initConfig?: Partial>, isOverwrite?: boolean) => Required>>>; +declare const useConfigKey: (key: string, init?: Init, isNew?: boolean) => AsyncResult; +declare const usePageConfig: (init?: Init) => any; + +export { useConfig, useConfigKey, useContext, useContextKey, useEnv, useEnvKey, useEnvKeyNew, usePageConfig, usePageContext, usePageEnv }; + +``` \ No newline at end of file diff --git a/src/index.ts b/src/index.ts new file mode 100644 index 0000000..fb1d399 --- /dev/null +++ b/src/index.ts @@ -0,0 +1,201 @@ +import { isBrowser } from './utils/browser.ts'; +import { getPathKey } from './utils/path-key.ts'; +import { BaseLoad } from '@kevisual/load'; + +const gt = (globalThis as any) || window || self; + +type GlobalContext = { + name?: string; + [key: string]: any; +} & T; +// 从window对象中获取全局的环境变量,如果没有则初始化一个 +export const useEnv = (initEnv?: GlobalContext, initKey = 'config', isOverwrite?: boolean) => { + const env: GlobalContext = gt[initKey]; + const _env = env || initEnv; + // 环境不存在则初始化一个 + if (!env) { + if (_env) { + gt[initKey] = _env; + } else { + gt[initKey] = {}; + } + } else if (isOverwrite) { + // 环境存在且是覆盖模式 + gt[initKey] = { ...env, ...initEnv }; + } + + return gt[initKey] as Required>; +}; +type InitResult = T | Promise | null; +type InitFn = () => T | Promise; +type Init = T | InitFn | null; +type AsyncResult = ASYNC extends true ? Promise : T; +type SimpleObject = Record; + +// 从全局环境变量中获取指定的key值,如果没有则初始化一个, key不存在,返回Env对象 +export const useEnvKey = (key: string, init?: Init, initKey = 'config'): InitResult => { + const _env = useEnv({}, initKey); + // 已经存在,直接返回 + if (key && typeof _env[key] !== 'undefined') { + return _env[key]; + } + // 不存在,但是有初始化函数,初始化的返回,同步函数,删除了重新加载? + if (key && init) { + if (typeof init !== 'function') { + _env[key] = init as T; + } + // 如果是函数,执行函数 + if (typeof init === 'function') { + const result = (init as InitFn)(); + // 如果结果是Promise,处理异步结果 + if (result instanceof Promise) { + return result.then((res) => { + _env[key] = res; + return res as T; + }); + } + _env[key] = result; + } + return _env[key]; + } + if (key) { + // 加载 + const baseLoad = new BaseLoad(); + const voidFn = async () => { + return _env[key]; + }; + const checkFn = async () => { + const loadRes = await baseLoad.load(voidFn, { + key, + isReRun: true, + checkSuccess: () => _env[key], + timeout: 5 * 60 * 1000, + interval: 1000, + // + }); + if (loadRes.code !== 200) { + console.error('load key error'); + return null; + } + return _env[key]; + }; + return checkFn(); + } + // 不存在,没有初始化函数 + console.error('key is empty '); + return null; +}; + +export const usePageEnv = (init?: Init, initKey = 'config') => { + const { id } = getPathKey(); + return useEnvKey(id, init, initKey); +}; +export const useEnvKeyNew = (key: string, initKey = 'config', opts?: { getNew?: boolean; init?: Init }) => { + const _env = useEnv({}, initKey); + if (key) { + delete _env[key]; + } + if (opts?.getNew && opts.init) { + return useEnvKey(key, opts.init, initKey); + } else if (opts?.getNew) { + return useEnvKey(key, null, initKey); + } +}; + +export const useContext = (initContext?: GlobalContext, isOverwrite?: boolean) => { + return useEnv(initContext, 'context', isOverwrite); +}; + +export const useContextKey = (key: string, init?: Init, isNew?: boolean): AsyncResult => { + if (isNew) { + return useEnvKeyNew(key, 'context', { getNew: true, init }) as any; + } + return useEnvKey(key, init, 'context') as any; +}; + +export const use = useContextKey; + +export const usePageContext = (init?: Init) => { + const { id } = getPathKey(); + return useContextKey(id, init); +}; + +export const useConfig = (initConfig?: Partial>, isOverwrite?: boolean) => { + return useEnv(initConfig, 'config', isOverwrite); +}; + +export const useConfigKey = (key: string, init?: Init, isNew?: boolean): AsyncResult => { + if (isNew) { + return useEnvKeyNew(key, 'config', { getNew: true, init }); + } + return useEnvKey(key, init, 'config') as any; +}; + +export const usePageConfig = (init?: Init) => { + const { id } = getPathKey(); + return useConfigKey(id, init); +}; + +class InitEnv { + static isInit = false; + + static init(opts?: { load?: boolean; page?: boolean }) { + if (InitEnv.isInit) { + return; + } + const { load = true, page = false } = opts || {}; + InitEnv.isInit = true; + // bind to window, 必须要的获取全局的环境变量 + // @ts-ignore + gt.useConfigKey = useConfigKey; + // @ts-ignore + gt.useContextKey = useContextKey; + // @ts-ignore + gt.use = use; + // @ts-ignore + gt.webEnv = { useConfigKey, useContextKey, use }; + // @ts-ignore + load && (gt.Load = BaseLoad); + } +} +InitEnv.init(); + + +/** + * 从环境变量获取 + * @param envKey + * @param initKey + * @returns + */ +export const useKey = (envKey: T, initKey = 'context') => { + let key: string = envKey as unknown as string; + if (!key) return null; + const _env = useEnv({}, initKey); + // 已经存在,直接返回 + if (typeof _env[key] !== 'undefined') { + return _env[key]; + } + if (!isBrowser) { + // 如果是node类,从process.env获取对应的环境变量 + const nodeEev = gt?.process?.env; + if (typeof nodeEev !== 'undefined') { + const value = nodeEev[key]; + if (typeof value !== 'undefined') { + return value; + } + } + } else { + /** + * 从浏览器的storage获取变量值 + */ + const storage = gt?.localStorage; + if (typeof storage !== 'undefined') { + const value = storage.getItem(key); + if (typeof value !== 'undefined') { + return value; + } + } + } + + return null; +} \ No newline at end of file diff --git a/src/test/a.ts b/src/test/a.ts new file mode 100644 index 0000000..e93f2c3 --- /dev/null +++ b/src/test/a.ts @@ -0,0 +1,33 @@ +import { useConfigKey, useConfig } from '@/index.ts'; +const sleep = (ms: number) => new Promise((resolve) => setTimeout(resolve, ms)); +const v = useConfigKey<{ name: string }>('a', async () => { + await sleep(1000); + return { + name: 'a', + }; +}); + +console.log('v', v.name); + +const v2 = await useConfigKey<{ name: string }, true>('a'); + +console.log('v2', v2.name); + +const v3 = useConfigKey<{ name: string }>('a', { + name: 'bbba', +}); + +console.log('v3', v3.name); + +const b = useConfig<{ f: string }>( + { + name: 'b', + age: 18, + getName: () => { + return 'b'; + }, + }, + true, +); + +console.log('b', b.name, b.age, b.getName?.()); diff --git a/src/utils/browser.ts b/src/utils/browser.ts new file mode 100644 index 0000000..7524531 --- /dev/null +++ b/src/utils/browser.ts @@ -0,0 +1 @@ +export const isBrowser = typeof window !== 'undefined' && typeof document !== 'undefined'; \ No newline at end of file diff --git a/src/utils/path-key.ts b/src/utils/path-key.ts new file mode 100644 index 0000000..7b59bb2 --- /dev/null +++ b/src/utils/path-key.ts @@ -0,0 +1,14 @@ +export const getPathKey = () => { + if (!isBrowser) { + return { path: '', key: '', id: '', prefix: '' }; + } + // 从localtion.href的路径中,/a/b 中 a为path,b为key + const pathname = location.pathname; + const paths = pathname.split('/'); + let [path, key] = paths.slice(1); + path = path || ''; + key = key || ''; + return { path, key, id: path + '---' + key, prefix: `/${path}/${key}` }; +}; + +const isBrowser = typeof window !== 'undefined' && typeof window.document !== 'undefined'; diff --git a/tsconfig.json b/tsconfig.json new file mode 100644 index 0000000..d93fb6a --- /dev/null +++ b/tsconfig.json @@ -0,0 +1,20 @@ +{ + "extends": "@kevisual/types/json/backend.json", + "compilerOptions": { + "baseUrl": "./", + "typeRoots": [ + "node_modules/@types", + ], + "paths": { + "@/*": [ + "src/*" + ], + "*": [ + "types/*" + ] + } + }, + "include": [ + "src/**/*.ts" + ], +} \ No newline at end of file