Skip to content

Commit

Permalink
Add mercylessly typing to container
Browse files Browse the repository at this point in the history
  • Loading branch information
TiMESPLiNTER committed Jan 31, 2024
1 parent 030d494 commit 53a50dd
Show file tree
Hide file tree
Showing 7 changed files with 6,979 additions and 59 deletions.
8 changes: 4 additions & 4 deletions src/container.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
import ServiceProvider from './serviceProvider';
export type ServiceKey<T> = keyof T;

export default interface Container
export default interface Container<T>
{
get(service: string): any;
get<K extends ServiceKey<T>>(key: K): T[K];

has(service: string): boolean;
has<K extends ServiceKey<T>>(key: K): boolean;
}
29 changes: 29 additions & 0 deletions src/foo.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
import Pimple from "./pimple.js";
import ServiceProvider from "./serviceProvider.js";

type ServiceMap = {
'foo.factory': string,
'bar': number,
43: number
}

class TestServiceProvider implements ServiceProvider<ServiceMap>
{
register(container: Pimple<ServiceMap>): void {
container.get('bar')
}

}

const pimple = new Pimple<ServiceMap>();

pimple.set('bar', (): number => {
return 42;
})

pimple.set('bar', 42);

pimple.set('foo.factory', () => 'hello world')

const bar = pimple.get('bar');
console.log(typeof bar, bar);
64 changes: 35 additions & 29 deletions src/pimple.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,14 @@
"use strict";

import Container from "./container";
import Container, {ServiceKey} from "./container";
import ServiceProvider from "./serviceProvider";

/** Declaration types */
type ServiceDeclaration = Function|Object;
type ProviderDeclaration = Function|ServiceProvider;
type ProviderDeclaration<T> = Function|ServiceProvider<T>;
type LazyServiceDefinition<T, S> = (container: Pimple<T>) => S;
type ProtectedServiceDefinition<T, S> = () => LazyServiceDefinition<T, S>;
type ServiceDefinition<T, S> = LazyServiceDefinition<T, S>|ProtectedServiceDefinition<T, S>|S;
type ServiceMap<T> = { [key in ServiceKey<T>]: ServiceDefinition<T, T[ServiceKey<T>]> };

/**
* Reserved names of properties
Expand All @@ -25,7 +28,7 @@ const reservedProperties: string[] = [
* @license LGPL
* @version 3.0.0
*/
export default class Pimple implements Container
export default class Pimple<T> implements Container<T>
{
/**
* @type {string}
Expand All @@ -36,39 +39,40 @@ export default class Pimple implements Container
* @type {{}}
* @private
*/
private _definitions: { [key: string]: any; } = {};
private _definitions: Partial<ServiceMap<T>> = {};

/**
* @type {{}}
* @private
*/
private _raw: { [key: string]: any; } = {};
private _raw: Partial<ServiceMap<T>> = {};

constructor(services: { [key: string]: any; } = {}) {
constructor(services: Partial<ServiceMap<T>> = {}) {
Object.keys(services).forEach((service) => {
this.set(service, services[service]);
const serviceKey = service as ServiceKey<T>;
this.set(serviceKey, services[serviceKey] as T[ServiceKey<T>]);
}, this);
}

/**
* Define a service
*/
public set(name: string, service: ServiceDeclaration): Pimple
public set<K extends ServiceKey<T>>(name: K, service: ServiceDefinition<T,T[K]>): Pimple<T>
{
this._raw[name] = service;

this._definitions[name] = service instanceof Function ?
(function () {
let cached: any;
return (pimple: Pimple) => {
return (pimple: Pimple<T>) => {
if (cached === undefined) {
cached = service(pimple);
}
return cached;
};
}()) : service;

if (reservedProperties.indexOf(name) === -1) {
if (reservedProperties.indexOf(name.toString()) === -1) {
Object.defineProperty(this, name, {
get: () => {
return this.get(name);
Expand All @@ -82,11 +86,11 @@ export default class Pimple implements Container
/**
* Register a factory
*/
public factory(name: string, callback: Function): Pimple {
public factory<K extends ServiceKey<T>>(name: K, callback: ServiceDefinition<T,T[K]>): Pimple<T> {
this._raw[name] = callback;
this._definitions[name] = callback;

if (reservedProperties.indexOf(name) === -1) {
if (reservedProperties.indexOf(name.toString()) === -1) {
Object.defineProperty(this, name, {
get: () => {
return this.get(name);
Expand All @@ -100,40 +104,38 @@ export default class Pimple implements Container
/**
* Get a service instance
*/
public get(name: string): any {
public get<K extends ServiceKey<T>>(name: K): T[K] {
if (this._definitions[name] instanceof Function) {
return this._definitions[name](this);
return (this._definitions[name] as LazyServiceDefinition<T, T[K]>)(this);
}
return this._definitions[name];
return this._definitions[name] as T[K];
}

/**
* Checks whether a service is registered or not
*/
public has(service: string): boolean {
return service in this._definitions;
public has<K extends ServiceKey<T>>(name: K): boolean {
return name in this._definitions;
}

/**
* Register a protected function
*/
public protect(service: Function): Function {
return () => {
return service;
};
public protect<K extends ServiceKey<T>>(key: K, service: T[K]): () => T[K] {
return () => service;
}

/**
* Extend a service
*/
public extend(serviceName: string, service: Function): Function {
public extend<K extends ServiceKey<T>>(serviceName: K, service: Function): Function {
if (!this._definitions[serviceName]) {
throw new RangeError(`Definition with "${serviceName}" not defined in container.`);
}

var def = this._definitions[serviceName];
let def = this._definitions[serviceName];

return this._definitions[serviceName] = (container: Pimple) => {
return this._definitions[serviceName] = (container: Pimple<T>) => {
if (def instanceof Function) {
def = def(container);
}
Expand All @@ -144,14 +146,18 @@ export default class Pimple implements Container
/**
* Get a service raw definition
*/
public raw(name: string): Function {
return this._raw[name];
public raw<K extends ServiceKey<T>>(name: K): ServiceDefinition<T, T[K]> {
if (!this._raw[name]) {
throw new RangeError();
}

return this._raw[name] as ServiceDefinition<T, T[K]>;
}

/**
* Register a service provider
*/
public register(provider: ProviderDeclaration): Pimple {
public register(provider: ProviderDeclaration<T>): Pimple<T> {
if (this.instanceOfServiceProvider(provider) && provider.register instanceof Function) {
provider.register(this);
return this;
Expand All @@ -165,7 +171,7 @@ export default class Pimple implements Container
return this;
}

private instanceOfServiceProvider(object: any): object is ServiceProvider {
private instanceOfServiceProvider(object: any): object is ServiceProvider<T> {
return 'register' in object;
}
}
4 changes: 2 additions & 2 deletions src/serviceProvider.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import Pimple from "./pimple";
/**
* Service provider class for service injecting in Pimple container
*/
export default interface ServiceProvider
export default interface ServiceProvider<T>
{
register(container: Pimple): void;
register(container: Pimple<T>): void;
}
Loading

0 comments on commit 53a50dd

Please sign in to comment.