import type { DataProviderKey, DataProviderKeyHost, } from "../../utils/dataProviderKey"; import DataProvider from "../../utils/PublisherProxy"; import { ConnectedComponent, setSubscribable } from "./common"; import { extractDynamicDependencies, resolveDynamicPath } from "./dynamicPath"; import { publishDynamicWatchKeys, registerDynamicPropertyWatcher, } from "./dynamicPropertyWatch"; import { getPublisherFromPath } from "./publisherPath"; /** * Publishes property writes to a publisher path. Inverse of @subscribe. * When the property is set, the value is written to the publisher (reflect-only, no subscription). * The decorated property is typed as `T` (or optional / `| null` / `| undefined` for Lit / TS 5). * Supports dynamic paths: use placeholders like "users.${userIndex}.email" in the DataProviderKey. * * @example * const formKey = new DataProviderKey("formData"); * @publish(formKey.email) * @state() * email = ""; * * // Dynamic path + hôte typé via le 2ᵉ générique de la clé : * @publish(new DataProviderKey("users.${userIndex}.email")) * email = ""; */ export function publish( key: DataProviderKey, ): ( target: DataProviderKeyHost & { [P in K]?: T | null | undefined }, propertyKey: K, ) => void { const path = key.path; const dynamicDependencies = extractDynamicDependencies(path); return function (target: object, propertyKey: string) { setSubscribable(target); const publisherKey = `__publish_${propertyKey}_publisher__`; const internalValueKey = `__publish_${propertyKey}_value__`; const existingDescriptor = Object.getOwnPropertyDescriptor( target as object, propertyKey, ); const initialValue = existingDescriptor && !existingDescriptor.get && !existingDescriptor.set ? existingDescriptor.value : undefined; Object.defineProperty(target as object, propertyKey, { get() { if (existingDescriptor?.get) { return existingDescriptor.get.call(this); } if ( !Object.prototype.hasOwnProperty.call(this, internalValueKey) && initialValue !== undefined ) { (this as Record)[internalValueKey] = initialValue; } return (this as Record)[internalValueKey]; }, set(newValue: unknown) { if (existingDescriptor?.set) { existingDescriptor.set.call(this, newValue); } else { (this as Record)[internalValueKey] = newValue; } const publisher = (this as Record)[publisherKey] as | DataProvider | undefined; if (publisher) { publisher.set(newValue); } }, enumerable: existingDescriptor?.enumerable ?? true, configurable: existingDescriptor?.configurable ?? true, }); (target as ConnectedComponent).__onConnected__((component) => { const comp = component as Record; const stateKey = `__publish_state_${propertyKey}`; type PublishState = { cleanupWatchers: Array<() => void> }; const state: PublishState = (comp[stateKey] as PublishState) || ((comp[stateKey] as PublishState) = { cleanupWatchers: [], }); const updatePublisher = () => { let resolvedPath: string | null; if (dynamicDependencies.length) { const resolution = resolveDynamicPath(component, path); resolvedPath = resolution.ready ? resolution.path : null; } else { resolvedPath = path; } const publisher = resolvedPath ? getPublisherFromPath(resolvedPath) : undefined; comp[publisherKey] = publisher ?? null; if (publisher && propertyKey in component) { const currentValue = comp[propertyKey]; if (currentValue !== undefined) { publisher.set(currentValue); } } }; state.cleanupWatchers.forEach((cleanup: () => void) => cleanup()); state.cleanupWatchers = []; if (dynamicDependencies.length) { for (const dependency of dynamicDependencies) { state.cleanupWatchers.push( registerDynamicPropertyWatcher( publishDynamicWatchKeys.watcherStore, publishDynamicWatchKeys.hooked, comp, dependency, updatePublisher, ), ); } } updatePublisher(); }); (target as ConnectedComponent).__onDisconnected__((component) => { const comp = component as Record; const stateKey = `__publish_state_${propertyKey}`; const state = comp[stateKey] as | { cleanupWatchers: Array<() => void> } | undefined; if (state?.cleanupWatchers) { state.cleanupWatchers.forEach((cleanup: () => void) => cleanup()); } comp[publisherKey] = undefined; }); } as ( target: DataProviderKeyHost & { [P in K]?: T | null | undefined }, propertyKey: K, ) => void; }