Skip to content

Commit

Permalink
chore(config-core): config use proxy
Browse files Browse the repository at this point in the history
  • Loading branch information
radiorz committed Jan 2, 2025
1 parent d8df260 commit 008bced
Show file tree
Hide file tree
Showing 16 changed files with 145 additions and 64 deletions.
82 changes: 57 additions & 25 deletions packages/config-core/lib/Config.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
import { clone, get, merge, set } from 'lodash';
import { ConfigSource } from './ConfigSource';
import { Emitter } from '@tikkhun/utils-core';
import { debounce, get, merge, set, throttle } from 'lodash';
import type { Api, GetOptions, RemoveOptions, SetOptions } from './Api';
import { ConfigStorage, MemoryStorage } from './ConfigStorage';
import { ConfigSource } from './ConfigSource';
import { createReactiveObject } from './reactive';
export interface ConfigOptions {
/**
* 获取来源
Expand All @@ -12,44 +12,64 @@ export interface ConfigOptions {
* 全局变量名称
*/
global?: string;
store?: ConfigStorage;
// store?: ConfigStorage; // store 永远是内存store 不过 source 可以save 也就是可以同步我们的更改。
}
export enum ConfigEvents {
change = 'change', // 整体改变了
valueChange = 'valueChange', // 某个值改变了
}

export class Config extends Emitter implements Api {
options: ConfigOptions;
sources: ConfigSource[] = [];
store: ConfigStorage;
// 由于可能会将value完全清空 所以还是多嵌套一层,让监听使用者不需要重新创建 new Proxy对象
_config = createReactiveObject<{ value: any }>(
{
value: {},
},
this.handleValueChange.bind(this),
);
get config() {
return this.store.get();
return this._config.value;
}
set config(value: any) {
this._config.value = value;
}
set config(value) {
// 目前总是整份保存
this.store.set(value);
// 更新也总是整份更新触发
this.emit('change', clone(this.config));
handleValueChange(path: string, value: any) {
console.log(`111 path,value`, path, value);
const theTruePath = path.replace(/value\.?/, '');
this.syncToSources(theTruePath, value);
this.emit(ConfigEvents.valueChange, { path: theTruePath, value });
// 这里会不断触发,知道 超出debounce时间(不过这样有一定的延迟,或许让业务直接监听valueChange 会更好)
this.handleWholeChange();
}
// 有时候不要频繁变更 所以加个debounce 只监听最后一次抖动
handleWholeChange() {
this.emit(ConfigEvents.change, this.config);
}
constructor(options: ConfigOptions) {
super();
this.options = options;
this.sources = this.options.sources;
this.store = this.options.store || new MemoryStorage();
this.on('change', (config) => {
// 同步到源上
this.sources.forEach((source) => {
source.save?.(config);
});
});
// 设置到全局
if (this.options.global) {
(globalThis as any)[this.options.global] = this;
}
// 修改多次最终只会触发一次
this.handleWholeChange = debounce(this.handleWholeChange, 100);
}
static create(options: ConfigOptions) {
const configManager = new Config(options);
configManager.init();
configManager.load();
return configManager;
}
syncToSources(path: string, value: any) {
// 同步到源上,源采取全修改的方式PUT
this.sources.forEach((source) => {
source.save?.(path, value);
});
}

init() {
// 目前都是同步.
this.sources.forEach((source) => {
Expand All @@ -59,12 +79,19 @@ export class Config extends Emitter implements Api {
load() {
// 目前都是同步
const results = this.sources.map((source) => source.load());
this.config = merge({}, ...results);
// 合并各个数据
const originConfig = merge({}, ...results);
// 使用Proxy来监听config的变化
this.config = originConfig;
}

addSource(source: ConfigSource) {
this.sources.push(source);
if (source.init) source.init();
this.config = merge({}, this.config, source?.load());
// this.config 不能直接等于
const sourceConfig = source?.load();
// 或者重新全部load也可以 不过这样开销比较大
this.config = merge({}, this.config, sourceConfig);
this.sources.push(source);
return this;
}
get(path: string): any;
Expand Down Expand Up @@ -92,7 +119,8 @@ export class Config extends Emitter implements Api {

const { path, data } = Object.assign({ path: '', data: undefined }, opts);
if (path === '') {
this.config = data || {}; // 清空配置
// 重置整个配置
this.config = {}; // 清空配置
return;
}
if (typeof data === 'undefined') return;
Expand All @@ -113,10 +141,14 @@ export class Config extends Emitter implements Api {
return this.set({ ...first, data: null });
}
// 重置整个config
reset() {
reset(source = false) {
// 清空所有,然后重新加载
// 清空所有 正常是直接load就行,但有种情况就是又是store 又是source 如果不清空就白重新加载。
this.remove();
if (source) {
this.sources.forEach((source) => {
source.reset?.();
});
}
// 重新加载
this.load();
}
Expand Down
3 changes: 2 additions & 1 deletion packages/config-core/lib/ConfigSource.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ export interface ConfigSource {
init?: () => void;
// 用来加载初始配置
load: () => Record<string, any>;
reset?: (path?: string) => void;
// 用来同步配置进行新的保存
save?: (config: Record<string, any>) => void;
save?: (path: string, value: any) => void;
}
5 changes: 0 additions & 5 deletions packages/config-core/lib/ConfigStorage/ConfigStorage.ts

This file was deleted.

11 changes: 0 additions & 11 deletions packages/config-core/lib/ConfigStorage/MemoryStorage.ts

This file was deleted.

2 changes: 0 additions & 2 deletions packages/config-core/lib/ConfigStorage/index.ts

This file was deleted.

1 change: 1 addition & 0 deletions packages/config-core/lib/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,3 +2,4 @@ export * from './Config';
export * from './ConfigSource';
export * from './Api';
export * from './EnvSource';
export * from './reactive';
36 changes: 36 additions & 0 deletions packages/config-core/lib/reactive.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
type ChangeHandler = (path: string, value: any) => void;

export function createReactiveObject<T extends object>(target: T, onChange: ChangeHandler, parentPath: string = ''): T {
const handler: ProxyHandler<T> = {
get(target: T, key: string | symbol, receiver: any): any {
const result = Reflect.get(target, key, receiver);
if (typeof result === 'object' && result !== null) {
return createReactiveObject(result, onChange, `${parentPath}${String(key)}.`);
}
return result;
},

set(target: T, key: string | symbol, value: any, receiver: any): boolean {
const ownTarget = receiver === target ? target : Object.create(target);
const oldValue = Reflect.get(ownTarget, key, receiver);

if (oldValue !== value) {
const path = `${parentPath}${String(key)}`;
onChange(path, value);
}

return Reflect.set(ownTarget, key, value, receiver);
},
};

return new Proxy(target, handler);
}

// // 使用示例
// function handleChange(path: string, value: any): void {
// console.log(`Property "${path}" changed to`, value);
// }

// const r = createReactiveObject({ a: { b: 1 } }, handleChange);

// r.a.b = 2; // 输出: Property "a.b" changed to 2
2 changes: 1 addition & 1 deletion packages/config-core/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@tikkhun/config-core",
"version": "0.1.15",
"version": "0.2.0",
"description": "",
"main": "dist/index.js",
"module": "dist/index.mjs",
Expand Down
25 changes: 17 additions & 8 deletions packages/config-core/src/index.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,4 @@
import { EnvSource } from '../lib';
import { Config } from '../lib';
import { removePrefix } from '../lib/EnvSource';
import { Config, EnvSource, ConfigEvents } from '../lib';
class TheEnvSource extends EnvSource {
initEnv(): boolean {
return true;
Expand All @@ -12,6 +10,12 @@ class TheEnvSource extends EnvSource {
a__cc: '777',
};
}
reset() {
console.log(`123`, 123);
}
save(path: string, value: any) {
console.log(`save path,value `, path, '%%%%', value);
}
}
const m = Config.create({
sources: [
Expand All @@ -29,14 +33,19 @@ const m = Config.create({
},
],
});
m.on('change', (config) => console.log('change', config.nnn));
console.log(`m.get()`, m.get());
m.on(ConfigEvents.change, (config) => {
console.log(`config change`, config);
});
m.on(ConfigEvents.valueChange, ({ path, value }) => console.log(`onvaluechange path,value`, path, value));
console.log(`获取全部 m.get()`, m.get());
// console.log(`m.get()`, JSON.stringify(m.get()));
console.log(`m.get('a.b.c')`, m.get('a.b.c'));
console.log(`通过路径获取 m.get('a.b.c')`, m.get('a.b.c'));
m.addSource({
load() {
return { nnn: '123' };
return { nnn: '123', mmm: '123' };
},
});
// console.log(`添加源 m.get('nnn')`, m.get('nnn'));
console.log(`修改nnn为 1234`);
m.set('nnn', '1234');
console.log(`m.get('nnn')`, m.get('nnn'));
console.log(`修改nnn为 1234后 m.get('nnn')`, m.get('nnn'));
3 changes: 2 additions & 1 deletion packages/config-core/todo.md
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
# TODO

- 软同步与硬同步?
- 软同步与硬同步?(这个不做)
- async init 和 load
- 细粒度更新(不是所有配置的变化)

# DONE

Expand Down
2 changes: 2 additions & 0 deletions packages/event/lib/Eventbus.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,8 @@
*/

export interface EventbusOptions {

delimiter: "/",

}

Expand Down
4 changes: 2 additions & 2 deletions packages/event/lib/Node.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,12 +27,12 @@ export class Node {
if (typeof this.options.name === 'function') {
return this.options.name();
}
return this.options.name!;
return this.options.name || this.constructor.name;
}
options: NodeOptions;
constructor(options?: Partial<NodeOptions>) {
this.options = optionsMerge(Node.defaultOptions, options);
this._name = this.getName();
}

join(node: Node) {}
}
9 changes: 8 additions & 1 deletion packages/result/src/index.ts
Original file line number Diff line number Diff line change
@@ -1 +1,8 @@
import {ResultManager} from './'
import { ResultFactory } from '../lib';
import zh from './zh';

const resultFactory = new ResultFactory({
friendlyMessageBuilder: (result) => {
return zh[result.bizChain.join('.')] ?? zh['default'];
},
});
8 changes: 8 additions & 0 deletions packages/result/src/zh.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
export default {
user: {
login: {
success: '登录成功',
error: '登录失败',
},
},
};
8 changes: 5 additions & 3 deletions packages/result/tsconfig.json
Original file line number Diff line number Diff line change
Expand Up @@ -28,8 +28,10 @@
"module": "commonjs" /* Specify what module code is generated. */,
// "rootDir": "./", /* Specify the root folder within your source files. */
"moduleResolution": "node" /* Specify how TypeScript looks up a file from a given module specifier. */,
// "baseUrl": "./", /* Specify the base directory to resolve non-relative module names. */
// "paths": {}, /* Specify a set of entries that re-map imports to additional lookup locations. */
"baseUrl": "./" /* Specify the base directory to resolve non-relative module names. */,
"paths": {
"@/*": ["./lib/*"],
} /* Specify a set of entries that re-map imports to additional lookup locations. */,
// "rootDirs": [], /* Allow multiple folders to be treated as one when resolving modules. */
// "typeRoots": [], /* Specify multiple folders that act like './node_modules/@types'. */
// "types": [], /* Specify type package names to be included without being referenced in a source file. */
Expand All @@ -39,7 +41,7 @@
// "resolvePackageJsonExports": true, /* Use the package.json 'exports' field when resolving package imports. */
// "resolvePackageJsonImports": true, /* Use the package.json 'imports' field when resolving imports. */
// "customConditions": [], /* Conditions to set in addition to the resolver-specific defaults when resolving imports. */
"resolveJsonModule": true, /* Enable importing .json files. */
"resolveJsonModule": true /* Enable importing .json files. */,
// "allowArbitraryExtensions": true, /* Enable importing files with any extension, provided a declaration file is present. */
// "noResolve": true, /* Disallow 'import's, 'require's or '<reference>'s from expanding the number of files TypeScript should add to a project. */

Expand Down
8 changes: 4 additions & 4 deletions tools/build-tools/lib/Build.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,10 +19,10 @@ import path from 'path';
import { Copier, CopierOptions } from './Copier';
import { Obfuscator, ObfuscatorOptions } from './Obfuscator';
export interface BuildOptions {
workspace: string;
outDir: string;
copyOptions: Partial<Omit<CopierOptions, 'outDir'>>;
obfuscateOptions: { enabled: boolean } & Partial<Omit<ObfuscatorOptions, 'outDir'>>;
workspace: string; // 需要编译的目录
outDir: string; // 输出目录
copyOptions: Partial<Omit<CopierOptions, 'outDir'>>; // 直接拷贝的选项
obfuscateOptions: { enabled: boolean } & Partial<Omit<ObfuscatorOptions, 'outDir'>>; // 混淆的选项
}
export class Build {
log = new Logger('Build');
Expand Down

0 comments on commit 008bced

Please sign in to comment.