services.ts

Home   »   services.ts

import { Service } from './services';

const _token = Symbol('MyService');
let instance: MyService = null;
export class MyService implements Service {
    constructor(token: Symbol) {
        if (token !== _token) {
            throw new Error('Invalid')
        }
    }

    public static get instance(): MyService {
        if (instance === null) {
            instance = new MyService(_token);
        }

        return instance;
    }

    public test(): string {
        return 'It Works!';
    }
}

import { MyService } from './my-service';
import { useService } from './services';

export default function Page() {
    const service = useService(MyService);
    
    return 

{ service.test() }

}
/**
 * Base interface to type the services
 */
export interface Service {}

/**
 * Wrapper function that takes a class containing a instance method or property, and gives back said instance
 * 
 * @param target The class that is going to be wrapped
 * @returns The instance of the service
 */
function Service(target: T): T {
    const desc = Object.getOwnPropertyDescriptors(target);

    // The class should have either a 'getInstance()' method, or a 'get instance()' property
    if (!desc.instance && !desc.getInstance) {
        throw new Error('Class does not have a instance property or getInstance method');
    } else {
        const instanceDescriptor = desc.instance || desc.getInstance;

        if (
            (instanceDescriptor.get && typeof instanceDescriptor.get === 'function') ||
            (instanceDescriptor.value && typeof instanceDescriptor.value === 'function')
        ) {
            const instanceFn: () => T = instanceDescriptor.get || instanceDescriptor.value;
            
            return instanceFn();
        } else {
            throw new Error('Invalid instance property or getInstance method');
        }
    }
}

// Map with each instance of the static singleton services
let services = {};

/**
 * React hook to handle static singletons
 * 
 * @param name The class name (not an instance)
 * @returns The instance of said class
 */
export function useService(name: Service): T {
    const serviceName = name.toString();

    if (!services[serviceName]) {
        services[serviceName] = Service(name);
    }

    return services[serviceName];
}

Leave a Reply

Your email address will not be published. Required fields are marked *