|
|
|
@ -30,13 +30,68 @@ import { get } from '../internal/client/runtime.js';
|
|
|
|
* @returns {TEntity}
|
|
|
|
* @returns {TEntity}
|
|
|
|
*/
|
|
|
|
*/
|
|
|
|
export const make_reactive = (Entity, options) => {
|
|
|
|
export const make_reactive = (Entity, options) => {
|
|
|
|
|
|
|
|
// we return a class so that the caller can call it with new
|
|
|
|
|
|
|
|
// @ts-ignore
|
|
|
|
|
|
|
|
return class {
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
|
|
* @param {ConstructorParameters<TEntity>} params
|
|
|
|
|
|
|
|
*/
|
|
|
|
|
|
|
|
constructor(...params) {
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
|
|
* each read method can be tracked like has, size, get and etc. these props might depend on a parameter. they have to reactive based on the
|
|
|
|
|
|
|
|
* parameter they depend on, if it requires a change. for instance if you call `set.has(2)` then call `set.add(5)` the former shouldn't get notified.
|
|
|
|
|
|
|
|
* so what is do we need to store if this needs to be generic? function name + parameter. unfortunately for each parameter we need a new signal, why?
|
|
|
|
|
|
|
|
* because arrays don't equal themselves even if they have the same value (referential equality).
|
|
|
|
|
|
|
|
* fortunately current builtins that we try to make reactive only take 1 parameter for their read methods so this is fine.
|
|
|
|
|
|
|
|
* @type {Map<string | symbol, Map<unknown[], import("#client").Source<boolean>>>}
|
|
|
|
|
|
|
|
**/
|
|
|
|
|
|
|
|
const read_methods_signals = new Map();
|
|
|
|
/**
|
|
|
|
/**
|
|
|
|
|
|
|
|
* other props that get notified based on any change listen to version
|
|
|
|
|
|
|
|
*/
|
|
|
|
|
|
|
|
const version_signal = source(false);
|
|
|
|
|
|
|
|
return new Proxy(new Entity(...params), {
|
|
|
|
|
|
|
|
get(target, property) {
|
|
|
|
|
|
|
|
const orig_property = target[property];
|
|
|
|
|
|
|
|
let result;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
if (typeof orig_property === 'function') {
|
|
|
|
|
|
|
|
// Bind functions directly to the `TEntity`
|
|
|
|
|
|
|
|
result = ((/** @type {unknown[]} */ ...params) => {
|
|
|
|
|
|
|
|
notify_if_required(
|
|
|
|
|
|
|
|
version_signal,
|
|
|
|
|
|
|
|
read_methods_signals,
|
|
|
|
|
|
|
|
property,
|
|
|
|
|
|
|
|
target,
|
|
|
|
|
|
|
|
options,
|
|
|
|
|
|
|
|
...params
|
|
|
|
|
|
|
|
);
|
|
|
|
|
|
|
|
return orig_property.bind(target)(...params);
|
|
|
|
|
|
|
|
}).bind(target);
|
|
|
|
|
|
|
|
} else {
|
|
|
|
|
|
|
|
// Properly handle getters
|
|
|
|
|
|
|
|
notify_if_required(version_signal, read_methods_signals, property, target, options);
|
|
|
|
|
|
|
|
result = Reflect.get(target, property, target);
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
return result;
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
|
|
* @template {new (...args: any) => any} TEntity
|
|
|
|
|
|
|
|
* @template {(keyof TEntityInstance)[]} TWriteProperties
|
|
|
|
|
|
|
|
* @template {(keyof TEntityInstance)[]} TReadProperties
|
|
|
|
* @template {InstanceType<TEntity>} TEntityInstance
|
|
|
|
* @template {InstanceType<TEntity>} TEntityInstance
|
|
|
|
* @template {keyof TEntityInstance} TProperty
|
|
|
|
* @template {keyof TEntityInstance} TProperty
|
|
|
|
* @param {import('#client').Source<boolean>} version_signal
|
|
|
|
* @param {import('#client').Source<boolean>} version_signal
|
|
|
|
* @param {ReadMethodsSignals} read_methods_signals
|
|
|
|
* @param {ReadMethodsSignals} read_methods_signals
|
|
|
|
* @param {TProperty} property
|
|
|
|
* @param {TProperty} property
|
|
|
|
* @param {TEntityInstance} entity_instance
|
|
|
|
* @param {TEntityInstance} entity_instance
|
|
|
|
|
|
|
|
* @param {Options<InstanceType<TEntity>, TWriteProperties, TReadProperties>} options
|
|
|
|
* @param {unknown[]} params
|
|
|
|
* @param {unknown[]} params
|
|
|
|
*/
|
|
|
|
*/
|
|
|
|
function notify_if_required(
|
|
|
|
function notify_if_required(
|
|
|
|
@ -44,29 +99,20 @@ export const make_reactive = (Entity, options) => {
|
|
|
|
read_methods_signals,
|
|
|
|
read_methods_signals,
|
|
|
|
property,
|
|
|
|
property,
|
|
|
|
entity_instance,
|
|
|
|
entity_instance,
|
|
|
|
|
|
|
|
options,
|
|
|
|
...params
|
|
|
|
...params
|
|
|
|
) {
|
|
|
|
) {
|
|
|
|
/**
|
|
|
|
|
|
|
|
* @param {TReadProperties} method_names
|
|
|
|
|
|
|
|
* @param {unknown[]} params
|
|
|
|
|
|
|
|
*/
|
|
|
|
|
|
|
|
function notify_read_methods(method_names, ...params) {
|
|
|
|
|
|
|
|
method_names.forEach((name) => {
|
|
|
|
|
|
|
|
if (DEV && !options.read_properties.includes(name)) {
|
|
|
|
|
|
|
|
throw new Error(
|
|
|
|
|
|
|
|
`when trying to notify reactions got a read method that wasn't defined in options: ${name.toString()}`
|
|
|
|
|
|
|
|
);
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
(params.length == 0 ? [null] : params).forEach((param) => {
|
|
|
|
|
|
|
|
const sig = get_signal_for_function(read_methods_signals, name, param);
|
|
|
|
|
|
|
|
increment_signal(version_signal, sig);
|
|
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
if (
|
|
|
|
if (
|
|
|
|
options.interceptors?.[property]?.(
|
|
|
|
options.interceptors?.[property]?.(
|
|
|
|
notify_read_methods,
|
|
|
|
(methods, ...params) => {
|
|
|
|
|
|
|
|
return notify_read_methods(
|
|
|
|
|
|
|
|
options,
|
|
|
|
|
|
|
|
version_signal,
|
|
|
|
|
|
|
|
read_methods_signals,
|
|
|
|
|
|
|
|
methods,
|
|
|
|
|
|
|
|
...params
|
|
|
|
|
|
|
|
);
|
|
|
|
|
|
|
|
},
|
|
|
|
entity_instance,
|
|
|
|
entity_instance,
|
|
|
|
property,
|
|
|
|
property,
|
|
|
|
...params
|
|
|
|
...params
|
|
|
|
@ -88,49 +134,36 @@ export const make_reactive = (Entity, options) => {
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// we return a class so that the caller can call it with new
|
|
|
|
|
|
|
|
// @ts-ignore
|
|
|
|
|
|
|
|
return class {
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
|
|
* @param {ConstructorParameters<TEntity>} params
|
|
|
|
|
|
|
|
*/
|
|
|
|
|
|
|
|
constructor(...params) {
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
|
|
* each read method can be tracked like has, size, get and etc. these props might depend on a parameter. they have to reactive based on the
|
|
|
|
|
|
|
|
* parameter they depend on, if it requires a change. for instance if you call `set.has(2)` then call `set.add(5)` the former shouldn't get notified.
|
|
|
|
|
|
|
|
* so what is do we need to store if this needs to be generic? function name + parameter. unfortunately for each parameter we need a new signal, why?
|
|
|
|
|
|
|
|
* because arrays don't equal themselves even if they have the same value (referential equality).
|
|
|
|
|
|
|
|
* fortunately current builtins that we try to make reactive only take 1 parameter for their read methods so this is fine.
|
|
|
|
|
|
|
|
* @type {Map<string | symbol, Map<unknown[], import("#client").Source<boolean>>>}
|
|
|
|
|
|
|
|
**/
|
|
|
|
|
|
|
|
const read_methods_signals = new Map();
|
|
|
|
|
|
|
|
/**
|
|
|
|
/**
|
|
|
|
* other props that get notified based on any change listen to version
|
|
|
|
* @template {new (...args: any) => any} TEntity
|
|
|
|
|
|
|
|
* @template {(keyof TEntityInstance)[]} TWriteProperties
|
|
|
|
|
|
|
|
* @template {(keyof TEntityInstance)[]} TReadProperties
|
|
|
|
|
|
|
|
* @template {InstanceType<TEntity>} TEntityInstance
|
|
|
|
|
|
|
|
* @param {import('#client').Source<boolean>} version_signal
|
|
|
|
|
|
|
|
* @param {ReadMethodsSignals} read_methods_signals
|
|
|
|
|
|
|
|
* @param {Options<InstanceType<TEntity>, TWriteProperties, TReadProperties>} options
|
|
|
|
|
|
|
|
* @param {TReadProperties} method_names
|
|
|
|
|
|
|
|
* @param {unknown[]} params
|
|
|
|
*/
|
|
|
|
*/
|
|
|
|
const version_signal = source(false);
|
|
|
|
function notify_read_methods(
|
|
|
|
return new Proxy(new Entity(...params), {
|
|
|
|
options,
|
|
|
|
get(target, property) {
|
|
|
|
version_signal,
|
|
|
|
const orig_property = target[property];
|
|
|
|
read_methods_signals,
|
|
|
|
let result;
|
|
|
|
method_names,
|
|
|
|
|
|
|
|
...params
|
|
|
|
if (typeof orig_property === 'function') {
|
|
|
|
) {
|
|
|
|
// Bind functions directly to the `TEntity`
|
|
|
|
method_names.forEach((name) => {
|
|
|
|
result = ((/** @type {unknown[]} */ ...params) => {
|
|
|
|
if (DEV && !options.read_properties.includes(name)) {
|
|
|
|
notify_if_required(version_signal, read_methods_signals, property, target, ...params);
|
|
|
|
throw new Error(
|
|
|
|
return orig_property.bind(target)(...params);
|
|
|
|
`when trying to notify reactions got a read method that wasn't defined in options: ${name.toString()}`
|
|
|
|
}).bind(target);
|
|
|
|
);
|
|
|
|
} else {
|
|
|
|
|
|
|
|
// Properly handle getters
|
|
|
|
|
|
|
|
notify_if_required(version_signal, read_methods_signals, property, target);
|
|
|
|
|
|
|
|
result = Reflect.get(target, property, target);
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
return result;
|
|
|
|
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
(params.length == 0 ? [null] : params).forEach((param) => {
|
|
|
|
|
|
|
|
const sig = get_signal_for_function(read_methods_signals, name, param);
|
|
|
|
|
|
|
|
increment_signal(version_signal, sig);
|
|
|
|
|
|
|
|
});
|
|
|
|
});
|
|
|
|
});
|
|
|
|
}
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
/**
|
|
|
|
* gets the signal for this function based on params. If the signal doesn't exist, it creates a new one and returns that
|
|
|
|
* gets the signal for this function based on params. If the signal doesn't exist, it creates a new one and returns that
|
|
|
|
|