Research
Security News
Malicious npm Packages Inject SSH Backdoors via Typosquatted Libraries
Socket’s threat research team has detected six malicious npm packages typosquatting popular libraries to insert SSH backdoors.
Zero Dependency, Minimalistic **Type-Safe DI Container** for TypeScript and JavaScript projects
Zero Dependency, Minimalistic Type-Safe DI Container for TypeScript and JavaScript projects
npm i --save true-di
yarn add true-di
The following code is an example solution that is based on the example used in the book "Dependency Injection Principles, Practices, and Patterns" by Steven van Deursen and Mark Seemann.
The problem is to display the list of featured products and discount their prices for the preferred customers.
More details on the example could be found in the "Getting Started" Example Project README file.
import Module from 'true-di';
import { DiscountService } from './DiscountService';
import { Product } from './domain/products';
import { ProductRepoMock } from './ProductRepoMock';
import { ProductService } from './ProductService';
import { UserService } from './UserService';
import PRODUCTS_JSON from './products.json';
const main = Module()
.private({
productRepo: () =>
new ProductRepoMock(PRODUCTS_JSON as Product[]),
})
.public({
userService: (_, { token }: { token: string | null }) =>
new UserService(token),
})
.private({
discountService: ({ userService }) =>
new DiscountService(userService),
})
.public({
productService: ({ productRepo, discountService }) =>
new ProductService(productRepo, discountService),
});
export default main;
import { IDiscountService, IUserProvider } from '../interfaces';
export const PREFERRED_CUSTOMER_DISCOUNT: number = 0.05;
export const NO_DISCOUNT: number = 0;
export class DiscountService implements IDiscountService {
constructor(
private readonly userService: IUserProvider,
) {}
async getDiscountRate() {
const user = await this.userService.getCurrentUser();
return user != null && user.isPreferredCustomer
? PREFERRED_CUSTOMER_DISCOUNT
: NO_DISCOUNT;
}
}
import { Product } from '../domain/products';
import { IProductRepo } from '../interfaces';
import { matches } from '../utils/matches';
export class ProductRepoMock implements IProductRepo {
constructor(private readonly products: Product[]) {}
async getProducts(match?: Partial<Product>): Promise<Product[]> {
return match != null ? this.products.filter(matches(match)) : this.products.slice();
}
}
import { parseToken } from '../domain/user-token';
import { User } from '../domain/users';
import { IUserService } from '../interfaces';
export class UserService implements IUserService {
#user: User | null = null;
#token: string | null = null;
constructor(readonly token: string | null) {
this.#token = token;
}
async getCurrentUser(): Promise<User | null> {
if (this.#user == null && this.#token != null) {
this.#user = parseToken(this.#token);
}
return this.#user;
}
}
import { applyDiscount } from '../domain/products';
import {
IDiscountRateProvider,
IProductService,
IProductsProvider,
} from '../interfaces';
export class ProductService implements IProductService {
constructor(
private readonly products: IProductsProvider,
private readonly discountService: IDiscountRateProvider,
) {}
async getFeaturedProducts() {
const discountRate = await this.discountService.getDiscountRate();
const featuredProducts = await this.products.getProducts({ isFeatured: true });
return discountRate > 0
? featuredProducts.map(applyDiscount(discountRate))
: featuredProducts;
}
}
import type { Request, Response } from 'express';
import { JSONMoneyReplacer } from './domain/money';
import { IFeaturedProductProvider } from './interfaces/IProductService';
type GetFeaturedProductsDeps = {
productService: IFeaturedProductProvider;
}
export const getFeaturedProducts =
(req: Request, res: Response) =>
async ({ productService }: GetFeaturedProductsDeps) => {
const featuredProducts = await productService.getFeaturedProducts();
res
.status(200)
.type('application/json')
.send(JSON.stringify(featuredProducts, JSONMoneyReplacer, 2));
};
import express from 'express';
import createContext from 'express-async-context';
import main from './main';
import { getFeaturedProducts } from './products-controller';
const app = express();
const Context = createContext(
req => main.create({ token: req.headers.authorization ?? null }),
);
app.use(Context.provider);
app.get('/featured-products', Context.consumer(getFeaturedProducts));
if (module === require.main) {
app.listen(8080, () => {
console.log('Server is listening on port: 8080');
console.log('Follow: http://localhost:8080/featured-products');
console.log(`
. .env && curl -H "authorization: \${USER_TOKEN}" http://localhost:8080/featured-products
`);
});
}
export default app;
In the context of true-di
, a Module is a collection of named and typed
items that can communicate with each other. These items can be either
private,meaning they are only accessible within the Module, or
public, which allows them to be accessed both inside and outside of the
Module.
Essentially, a Module can be thought of as an instance of a class, with
private and public fields. However, in true-di
, none of the items within the
Module are created until they are requested from outside of the module,
including the function provided to the .expose
method of the ModuleBuilder.
The Module could be created by invoking the Module Factory function.
The Module Factory is a function that takes an object called external dependencies as input and uses it to create a Module. Later on, when an item within the Module is requested, the corresponding Resolver will receive the external dependencies as input to resolve the item.
In true-di
, the Module Factory is essentially a composition of Resolvers
that have been defined for each item within the module. This means that the code
defining the Module Factory serves as the Composition Root, where all loosely
coupled components such as classes and functions are brought together and
assembled in a cohesive manner.
The Resolver is an item factory, that receives two parameters:
Resolver is an implementation of the Resolution Rule, the rule defines how to create a specific item using other items of Module and external dependencies.
Unlike some other dependency injection solutions, true-di
does not rely on interfaces
or type names to define how to create an item. Instead, it uses explicit item
names and Resolvers defined for each name. This approach avoids the need for
additional abstractions in cases where a single interface is implemented by
several different components (classes).
Also it allows to use true-di
with JS-projects those don't have any interfaces
or type names nor in build time, nor in runtime.
Initializer - is a function associated with a some item of Module that receives two arguments:
And initializes the item.
The Initializer is an implement of the "Injection via property" pattern that is useful to resolve cyclic dependencies.
Note: The Cyclic Dependencies is an anti-pattern and should be avoided whenever it is possible.
The Initializers is an object that maps item names to correspondent item Initializer
FAQs
Zero Dependency, Minimalistic **Type-Safe DI Container** for TypeScript and JavaScript projects
We found that true-di demonstrated a not healthy version release cadence and project activity because the last version was released a year ago. It has 1 open source maintainer collaborating on the project.
Did you know?
Socket for GitHub automatically highlights issues in each pull request and monitors the health of all your open source dependencies. Discover the contents of your packages and block harmful activity before you install or update your dependencies.
Research
Security News
Socket’s threat research team has detected six malicious npm packages typosquatting popular libraries to insert SSH backdoors.
Security News
MITRE's 2024 CWE Top 25 highlights critical software vulnerabilities like XSS, SQL Injection, and CSRF, reflecting shifts due to a refined ranking methodology.
Security News
In this segment of the Risky Business podcast, Feross Aboukhadijeh and Patrick Gray discuss the challenges of tracking malware discovered in open source softare.