/** * Type-safe navigation through composite data structures (publisher paths). * Each property or index access extends the path. Retrieve the final path via toString() or path. * Supports dynamic paths: use placeholders like "users.${userIndex}" in the constructor. * * @example * const myKey = new DataProviderKey("data").items[0]; * myKey.toString(); // "data.items.0" * myKey.path; // same * * @example * // U = dépendances dynamiques sur l’hôte (voir DataProviderKeyHost) — propagé sur .foo.bar * new DataProviderKey("demoUsers.${userIndex}"); */ type IsAny = 0 extends 1 & T ? true : false; /** * Prototype de classe décorée : propriétés minimales attendues sur l’hôte quand la clé est * `DataProviderKey<…, U>` (U renseigné à la construction). Avec `U` par défaut (`any`), pas de contrainte. */ export type DataProviderKeyHost = IsAny extends true ? object : keyof U extends never ? object : object & U; /** * Navigation « dot » : une propriété optionnelle `foo?: X` reste traversable (le chemin publisher * existe). On retire `null` / `undefined` seulement quand la partie non-nulle est un `object` * (y compris tableaux, tuples), pour que le chaînage `.bar.baz` soit typé sans `?.`. * Les feuilles (`boolean | undefined`, `string`, etc.) gardent leur union telle quelle. */ type DataProviderKeyNavigate = T extends (...args: unknown[]) => unknown ? T : NonNullable extends infer U ? [U] extends [never] ? T : U extends object ? U : T : T; /** * U : forme minimale du composant pour résoudre les placeholders `${…}` du path ; inchangée lors de la navigation. */ type DataProviderKeyProxy = NonNullable extends object ? { [K in keyof NonNullable as NonNullable[K] extends ( ...args: unknown[] ) => unknown ? never : K]-?: DataProviderKey< DataProviderKeyNavigate[K]>, U >; } : object; export type DataProviderKey = DataProviderKeyImpl & DataProviderKeyProxy; class DataProviderKeyImpl { declare readonly _phantom?: T; declare readonly _phantomDeps?: U; constructor(public readonly path: string) {} toString(): string { return this.path; } } function createDataProviderKeyProxy( key: DataProviderKeyImpl, ): DataProviderKey { return new Proxy(key, { get(target, prop: string | symbol) { if (prop === "path") return target.path; if (prop === "toString") return target.toString.bind(target); if (prop === Symbol.toStringTag) return "DataProviderKey"; if (typeof prop === "symbol") return (target as unknown as Record)[prop]; const newPath = target.path ? `${target.path}.${String(prop)}` : String(prop); return createDataProviderKeyProxy( new DataProviderKeyImpl(newPath), ); }, }) as DataProviderKey; } export interface DataProviderKeyConstructor { new (path: string): DataProviderKey; } /* eslint-disable @typescript-eslint/no-explicit-any */ export const DataProviderKey: DataProviderKeyConstructor = function ( this: any, path: string, ): DataProviderKey { if (!(this instanceof DataProviderKey)) { return new (DataProviderKey as DataProviderKeyConstructor)(path); } return createDataProviderKeyProxy(new DataProviderKeyImpl(path)); } as any;