chore: run format and lint

This commit is contained in:
Stevan Freeborn
2025-05-24 22:29:46 -05:00
parent a8145ff5bb
commit 00b3b83074
4 changed files with 43 additions and 41 deletions
+2 -5
View File
@@ -1,6 +1,3 @@
{ {
"cSpell.words": [ "cSpell.words": ["netdi", "stevanfreeborn"]
"netdi", }
"stevanfreeborn"
]
}
+22 -16
View File
@@ -29,7 +29,13 @@ pnpm add @stevanfreeborn/hono-netdi hono
```typescript ```typescript
import { Hono } from 'hono'; import { Hono } from 'hono';
import { ServiceCollection, createServiceIdentifier, injectable, injectServices, useService } from '@stevanfreeborn/hono-netdi'; import {
ServiceCollection,
createServiceIdentifier,
injectable,
injectServices,
useService,
} from '@stevanfreeborn/hono-netdi';
// Define your service interface and implementation // Define your service interface and implementation
interface IUserService { interface IUserService {
@@ -56,7 +62,7 @@ const app = new Hono();
app.use(injectServices(serviceProvider)); app.use(injectServices(serviceProvider));
// Use services in your routes // Use services in your routes
app.get('/users/:id', async (c) => { app.get('/users/:id', async c => {
const userService = useService(c, IUserService); const userService = useService(c, IUserService);
const user = await userService.getUser(c.req.param('id')); const user = await userService.getUser(c.req.param('id'));
return c.json(user); return c.json(user);
@@ -135,7 +141,7 @@ import { injectable, inject } from '@stevanfreeborn/hono-netdi';
class UserService implements IUserService { class UserService implements IUserService {
constructor( constructor(
@inject(IUserRepository) private userRepository: IUserRepository, @inject(IUserRepository) private userRepository: IUserRepository,
@inject(ILogger) private logger: ILogger @inject(ILogger) private logger: ILogger,
) {} ) {}
} }
``` ```
@@ -232,7 +238,7 @@ class UserRepository implements IUserRepository {
class UserService implements IUserService { class UserService implements IUserService {
constructor( constructor(
@inject(IUserRepository) private userRepository: IUserRepository, @inject(IUserRepository) private userRepository: IUserRepository,
@inject(ILogger) private logger: ILogger @inject(ILogger) private logger: ILogger,
) {} ) {}
async getUser(id: string): Promise<User> { async getUser(id: string): Promise<User> {
@@ -284,13 +290,13 @@ services.addScoped(EmailNotification, EmailNotificationService);
services.addScoped(SmsNotification, SmsNotificationService); services.addScoped(SmsNotification, SmsNotificationService);
// Use in routes // Use in routes
app.post('/notify', async (c) => { app.post('/notify', async c => {
const emailService = useService(c, EmailNotification); const emailService = useService(c, EmailNotification);
const smsService = useService(c, SmsNotification); const smsService = useService(c, SmsNotification);
await emailService.send('Hello via email!'); await emailService.send('Hello via email!');
await smsService.send('Hello via SMS!'); await smsService.send('Hello via SMS!');
return c.json({ success: true }); return c.json({ success: true });
}); });
``` ```
@@ -326,10 +332,10 @@ const IDatabase = createServiceIdentifier<IDatabase>();
services.addSingleton<IDatabaseConfig>(IDatabaseConfig, () => ({ services.addSingleton<IDatabaseConfig>(IDatabaseConfig, () => ({
connectionString: process.env.DB_CONNECTION_STRING!, connectionString: process.env.DB_CONNECTION_STRING!,
timeout: 30000 timeout: 30000,
})); }));
services.addScoped(IDatabase, (provider) => { services.addScoped(IDatabase, provider => {
const config = provider.getService(IDatabaseConfig); const config = provider.getService(IDatabaseConfig);
return new Database(config); return new Database(config);
}); });
@@ -340,9 +346,9 @@ services.addScoped(IDatabase, (provider) => {
The middleware automatically handles service scope disposal even when errors occur: The middleware automatically handles service scope disposal even when errors occur:
```typescript ```typescript
app.get('/error-example', async (c) => { app.get('/error-example', async c => {
const userService = useService(c, IUserService); const userService = useService(c, IUserService);
try { try {
// This might throw an error // This might throw an error
const user = await userService.getUser('invalid-id'); const user = await userService.getUser('invalid-id');
@@ -369,7 +375,7 @@ app.use(cors());
app.use(logger()); app.use(logger());
// Routes can now use services // Routes can now use services
app.get('/', (c) => { app.get('/', c => {
const service = useService(c, IMyService); const service = useService(c, IMyService);
return c.json(service.getData()); return c.json(service.getData());
}); });
@@ -408,7 +414,7 @@ const ILogger = createServiceIdentifier<ILogger>();
@injectable() @injectable()
class UserService { class UserService {
constructor(@inject(ILogger) private logger: ILogger) {} constructor(@inject(ILogger) private logger: ILogger) {}
async getUser(id: string): Promise<User> { async getUser(id: string): Promise<User> {
this.logger.log(`Getting user ${id}`); this.logger.log(`Getting user ${id}`);
// Implementation // Implementation
@@ -444,7 +450,7 @@ Keep dependencies minimal and well-defined:
class UserService implements IUserService { class UserService implements IUserService {
constructor( constructor(
@inject(IUserRepository) private userRepository: IUserRepository, @inject(IUserRepository) private userRepository: IUserRepository,
@inject(ILogger) private logger: ILogger @inject(ILogger) private logger: ILogger,
) {} ) {}
} }
@@ -471,10 +477,10 @@ class UserService implements IUserService {
```typescript ```typescript
// ✅ Correct order // ✅ Correct order
app.use(injectServices(serviceProvider)); app.use(injectServices(serviceProvider));
app.get('/', (c) => useService(c, IMyService)); app.get('/', c => useService(c, IMyService));
// ❌ Wrong order // ❌ Wrong order
app.get('/', (c) => useService(c, IMyService)); app.get('/', c => useService(c, IMyService));
app.use(injectServices(serviceProvider)); app.use(injectServices(serviceProvider));
``` ```
+16 -16
View File
@@ -17,35 +17,35 @@ declare module 'hono' {
/** /**
* Creates a Hono middleware that manages dependency injection service scopes for each request. * Creates a Hono middleware that manages dependency injection service scopes for each request.
* *
* This middleware creates a new service scope at the beginning of each request and automatically * This middleware creates a new service scope at the beginning of each request and automatically
* disposes of it when the request completes, ensuring proper resource cleanup and preventing * disposes of it when the request completes, ensuring proper resource cleanup and preventing
* memory leaks. The service scope is stored in the Hono context and can be accessed by subsequent * memory leaks. The service scope is stored in the Hono context and can be accessed by subsequent
* middleware and route handlers. * middleware and route handlers.
* *
* @param serviceProvider - The root service provider from which to create scoped instances * @param serviceProvider - The root service provider from which to create scoped instances
* @returns A Hono middleware handler that manages service scope lifecycle * @returns A Hono middleware handler that manages service scope lifecycle
* *
* @example * @example
* ```typescript * ```typescript
* import { Hono } from 'hono'; * import { Hono } from 'hono';
* import { injectServices, useService, ServiceCollection } from '@stevanfreeborn/hono-netdi'; * import { injectServices, useService, ServiceCollection } from '@stevanfreeborn/hono-netdi';
* *
* // Configure services * // Configure services
* const services = new ServiceCollection(); * const services = new ServiceCollection();
* services.addScoped(MyService); * services.addScoped(MyService);
* const serviceProvider = services.build(); * const serviceProvider = services.build();
* *
* // Create Hono app with DI middleware * // Create Hono app with DI middleware
* const app = new Hono(); * const app = new Hono();
* app.use(injectServices(serviceProvider)); * app.use(injectServices(serviceProvider));
* *
* app.get('/', (c) => { * app.get('/', (c) => {
* const myService = useService(c, MyService); * const myService = useService(c, MyService);
* return c.json({ data: myService.getData() }); * return c.json({ data: myService.getData() });
* }); * });
* ``` * ```
* *
* @throws {Error} If the service provider is null or undefined * @throws {Error} If the service provider is null or undefined
* @see {@link useService} for accessing services within request handlers * @see {@link useService} for accessing services within request handlers
*/ */
@@ -65,40 +65,40 @@ export function injectServices(serviceProvider: IServiceProvider): MiddlewareHan
/** /**
* Retrieves a service instance from the current request's dependency injection scope. * Retrieves a service instance from the current request's dependency injection scope.
* *
* This function extracts the service scope from the Hono context (which must have been * This function extracts the service scope from the Hono context (which must have been
* set by the `injectServices` middleware) and uses it to resolve the requested service. * set by the `injectServices` middleware) and uses it to resolve the requested service.
* Services are resolved according to their configured lifetime (singleton, scoped, or transient). * Services are resolved according to their configured lifetime (singleton, scoped, or transient).
* *
* @template T - The type of service to retrieve * @template T - The type of service to retrieve
* @param c - The Hono context containing the service scope * @param c - The Hono context containing the service scope
* @param serviceType - The service identifier used to resolve the service instance * @param serviceType - The service identifier used to resolve the service instance
* @returns The resolved service instance of type T * @returns The resolved service instance of type T
* *
* @example * @example
* ```typescript * ```typescript
* import { Context } from 'hono'; * import { Context } from 'hono';
* import { useService, createServiceIdentifier } from '@stevanfreeborn/hono-netdi'; * import { useService, createServiceIdentifier } from '@stevanfreeborn/hono-netdi';
* *
* interface IUserService { * interface IUserService {
* getUser(id: string): Promise<User>; * getUser(id: string): Promise<User>;
* } * }
* *
* const userServiceId = createServiceIdentifier<IUserService>(); * const userServiceId = createServiceIdentifier<IUserService>();
* *
* app.get('/users/:id', async (c: Context) => { * app.get('/users/:id', async (c: Context) => {
* const userService = useService(c, userServiceId); * const userService = useService(c, userServiceId);
* const user = await userService.getUser(c.req.param('id')); * const user = await userService.getUser(c.req.param('id'));
* return c.json(user); * return c.json(user);
* }); * });
* ``` * ```
* *
* @throws {Error} When the service scope is not found in the context (typically when * @throws {Error} When the service scope is not found in the context (typically when
* `injectServices` middleware was not properly configured) * `injectServices` middleware was not properly configured)
* @throws {Error} When the service scope is not a valid ServiceScope instance * @throws {Error} When the service scope is not a valid ServiceScope instance
* @throws {Error} When the requested service cannot be resolved (service not registered, * @throws {Error} When the requested service cannot be resolved (service not registered,
* missing dependencies, etc.) * missing dependencies, etc.)
* *
* @see {@link injectServices} for setting up the dependency injection middleware * @see {@link injectServices} for setting up the dependency injection middleware
*/ */
export function useService<T>(c: Context, serviceType: ServiceIdentifier<T>): T { export function useService<T>(c: Context, serviceType: ServiceIdentifier<T>): T {
@@ -117,4 +117,4 @@ export function useService<T>(c: Context, serviceType: ServiceIdentifier<T>): T
return scope.serviceProvider.getService(serviceType); return scope.serviceProvider.getService(serviceType);
} }
export * from '@stevanfreeborn/netdi'; export * from '@stevanfreeborn/netdi';
+3 -4
View File
@@ -48,7 +48,7 @@ describe('injectServices', () => {
describe('useService', () => { describe('useService', () => {
test('it should throw an error if the service scope is not found in context', async () => { test('it should throw an error if the service scope is not found in context', async () => {
type ITestService = object; type ITestService = object;
const serviceId = createServiceIdentifier<ITestService>(); const serviceId = createServiceIdentifier<ITestService>();
const app = new Hono<Env>(); const app = new Hono<Env>();
@@ -68,13 +68,13 @@ describe('useService', () => {
test('it should throw an error if the service scope is not an instance of ServiceScope', async () => { test('it should throw an error if the service scope is not an instance of ServiceScope', async () => {
type ITestService = object; type ITestService = object;
const serviceId = createServiceIdentifier<ITestService>(); const serviceId = createServiceIdentifier<ITestService>();
const app = new Hono<Env>(); const app = new Hono<Env>();
app.get('/', c => { app.get('/', c => {
c.set('serviceScope', {} as IServiceScope); c.set('serviceScope', {} as IServiceScope);
try { try {
useService(c, serviceId); useService(c, serviceId);
return c.text('Service scope found', 200); return c.text('Service scope found', 200);
@@ -115,7 +115,6 @@ describe('useService', () => {
expect(res.status).toBe(500); expect(res.status).toBe(500);
}); });
test('it should return the service from the service scope', async () => { test('it should return the service from the service scope', async () => {
interface ITestService { interface ITestService {
id(): string; id(): string;