2026-04-01 13:36:16 +08:00
|
|
|
import {Signal, signal} from "@preact/signals-core";
|
|
|
|
|
|
|
|
|
|
export type Entity = {
|
|
|
|
|
id: string;
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
export type EntityAccessor<T extends Entity> = {
|
|
|
|
|
id: string;
|
|
|
|
|
value: T;
|
|
|
|
|
}
|
|
|
|
|
|
2026-04-01 22:55:59 +08:00
|
|
|
function createReactiveProxy<T extends Entity>(entitySignal: Signal<T>): T {
|
|
|
|
|
return new Proxy({} as T, {
|
|
|
|
|
get(_target, prop) {
|
|
|
|
|
const current = entitySignal.value;
|
|
|
|
|
const value = current[prop as keyof T];
|
|
|
|
|
if (typeof value === 'function') {
|
|
|
|
|
return value.bind(current);
|
|
|
|
|
}
|
|
|
|
|
return value;
|
|
|
|
|
},
|
|
|
|
|
set(_target, prop, value) {
|
|
|
|
|
const current = entitySignal.value;
|
|
|
|
|
entitySignal.value = { ...current, [prop]: value };
|
|
|
|
|
return true;
|
|
|
|
|
},
|
|
|
|
|
ownKeys(_target) {
|
|
|
|
|
return Reflect.ownKeys(entitySignal.value);
|
|
|
|
|
},
|
|
|
|
|
getOwnPropertyDescriptor(_target, prop) {
|
|
|
|
|
return Reflect.getOwnPropertyDescriptor(entitySignal.value, prop);
|
|
|
|
|
},
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function createReactiveAccessor<T extends Entity>(id: string, entitySignal: Signal<T>): EntityAccessor<T> {
|
|
|
|
|
const proxy = createReactiveProxy(entitySignal);
|
|
|
|
|
return {
|
|
|
|
|
id,
|
|
|
|
|
get value() {
|
|
|
|
|
return proxy;
|
|
|
|
|
},
|
|
|
|
|
set value(value: T) {
|
|
|
|
|
entitySignal.value = value;
|
|
|
|
|
}
|
|
|
|
|
} as EntityAccessor<T>;
|
|
|
|
|
}
|
|
|
|
|
|
2026-04-01 13:36:16 +08:00
|
|
|
export function createEntityCollection<T extends Entity>() {
|
|
|
|
|
const collection = signal({} as Record<string, Signal<T>>);
|
|
|
|
|
const remove = (...ids: string[]) => {
|
|
|
|
|
collection.value = Object.fromEntries(
|
|
|
|
|
Object.entries(collection.value).filter(([id]) => !ids.includes(id)),
|
|
|
|
|
);
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
const add = (...entities: T[]) => {
|
|
|
|
|
collection.value = {
|
|
|
|
|
...collection.value,
|
|
|
|
|
...Object.fromEntries(entities.map((entity) => [entity.id, signal(entity)])),
|
|
|
|
|
};
|
|
|
|
|
};
|
|
|
|
|
|
2026-04-01 22:55:59 +08:00
|
|
|
const get = (id: string): EntityAccessor<T> => {
|
|
|
|
|
const entitySignal = collection.value[id];
|
|
|
|
|
if (!entitySignal) {
|
|
|
|
|
return {
|
|
|
|
|
id,
|
|
|
|
|
get value() {
|
|
|
|
|
return undefined as unknown as T;
|
|
|
|
|
},
|
|
|
|
|
set value(_value: T) {}
|
|
|
|
|
} as EntityAccessor<T>;
|
2026-04-01 13:36:16 +08:00
|
|
|
}
|
2026-04-01 22:55:59 +08:00
|
|
|
return createReactiveAccessor(id, entitySignal);
|
2026-04-01 13:36:16 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return {
|
|
|
|
|
collection,
|
|
|
|
|
remove,
|
|
|
|
|
add,
|
|
|
|
|
get
|
|
|
|
|
}
|
|
|
|
|
}
|