Skip to content

Commit

Permalink
feat: dynamic module and inject plain values
Browse files Browse the repository at this point in the history
  • Loading branch information
Sorikairox committed Feb 25, 2024
1 parent 4f5c68f commit 9468a0e
Show file tree
Hide file tree
Showing 10 changed files with 79 additions and 52 deletions.
5 changes: 3 additions & 2 deletions spec/injection.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ import { GLOBAL_GUARD } from '../src/guard/constants.ts';
import { AuthGuard } from '../src/guard/interface.ts';
import { Inject } from '../src/injector/decorator.ts';
import { Injectable, SCOPE } from '../src/injector/injectable/decorator.ts';
import { Module, ModuleOptions } from '../src/module/decorator.ts';
import { Module, ModuleMetadata } from '../src/module/decorator.ts';
import { Controller, Get, Post } from '../src/router/controller/decorator.ts';
import { HttpContext } from '../src/router/router.ts';
import { injector } from '../src/injector/injector.ts';
Expand Down Expand Up @@ -118,11 +118,12 @@ Deno.test('Injection', async (testContext) => {
},
},
],
module: SecondModule,
};
}
}

const firstModuleOption: ModuleOptions = {
const firstModuleOption: ModuleMetadata = {
imports: [SecondModule.forRoot()],
controllers: [FirstController],
injectables: [
Expand Down
14 changes: 6 additions & 8 deletions spec/queue.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,13 +5,11 @@ import {
KvQueue,
KvQueueModule,
Module,
OnEvent,
OnQueueMessage,
} from '../mod.ts';
import {
assertEquals,
assertSpyCall,
assertThrows,
spy,
} from '../src/deps_test.ts';

Expand Down Expand Up @@ -58,12 +56,10 @@ Deno.test('Queue Module', async (t) => {
injectables: [TestListener],
})
class TestModule {}

const application = new DanetApplication();
try {
await application.init(TestModule);
const listenerInfo = await application.listen(0);

await t.step('validate if api call send message in queue', async () => {
assertEquals(callback.calls.length, 0);

let res = await fetch(`http://localhost:${listenerInfo.port}/trigger`);
Expand All @@ -86,7 +82,9 @@ Deno.test('Queue Module', async (t) => {
assertSpyCall(secondCallback, 0, {
args: ['toto'],
});
});

await application.close();
await application.close();
} catch (e) {
await application.close();
throw e;
}
});
24 changes: 13 additions & 11 deletions src/app.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ import { hookName } from './hook/interfaces.ts';
import { injector } from './injector/injector.ts';
import { Logger } from './logger.ts';
import { MetadataHelper } from './metadata/helper.ts';
import { moduleMetadataKey, ModuleOptions } from './module/decorator.ts';
import { moduleMetadataKey, ModuleMetadata } from './module/decorator.ts';
import { HandlebarRenderer } from './renderer/handlebar.ts';
import { DanetRouter } from './router/router.ts';
import { Constructor } from './utils/constructor.ts';
Expand All @@ -16,7 +16,7 @@ import { globalMiddlewareContainer } from './router/middleware/global-container.
import { ModuleConstructor } from './module/constructor.ts';
import { serveStatic } from './utils/serve-static.ts';
import { cors } from 'https://deno.land/x/hono/middleware.ts';
import { ModuleInstance } from './mod.ts';
import { DynamicModule } from './mod.ts';

type CORSOptions = {
origin: string | string[] | ((origin: string) => string | undefined | null);
Expand Down Expand Up @@ -48,26 +48,28 @@ export class DanetApplication {
return this.injector.get(Type);
}

async bootstrap(ModuleInstanceOrConstructor: Constructor | ModuleInstance) {
async bootstrap(NormalOrDynamicModule: Constructor | DynamicModule) {
// deno-lint-ignore no-explicit-any
const possibleModuleInstance = ModuleInstanceOrConstructor as any;
let instance: ModuleInstance;
const possibleModuleInstance = NormalOrDynamicModule as any;
let instance: ModuleMetadata;

if (
!possibleModuleInstance.imports &&
!possibleModuleInstance.injectables
!possibleModuleInstance.module
) {
instance =
new (ModuleInstanceOrConstructor as Constructor)() as ModuleInstance;
const metadata: ModuleOptions = MetadataHelper.getMetadata<ModuleOptions>(
new (NormalOrDynamicModule as Constructor)() as DynamicModule;
const metadata: ModuleMetadata = MetadataHelper.getMetadata<ModuleMetadata>(
moduleMetadataKey,
ModuleInstanceOrConstructor,
NormalOrDynamicModule,
);
instance.controllers = metadata.controllers;
instance.imports = metadata.imports;
instance.injectables = metadata.injectables;
} else {
instance = ModuleInstanceOrConstructor as ModuleInstance;
instance = new ((NormalOrDynamicModule as DynamicModule).module)() as ModuleMetadata;
instance.controllers = (NormalOrDynamicModule as DynamicModule).controllers;
instance.imports = (NormalOrDynamicModule as DynamicModule).imports;
instance.injectables = (NormalOrDynamicModule as DynamicModule).injectables;
}

for (const module in instance?.imports) {
Expand Down
8 changes: 6 additions & 2 deletions src/hook/executor.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { InjectableHelper } from '../injector/injectable/helper.ts';
import { Injector } from '../injector/injector.ts';
import { MetadataHelper } from '../metadata/helper.ts';
import { hookName } from './interfaces.ts';

export class HookExecutor {
Expand All @@ -9,8 +10,11 @@ export class HookExecutor {
public async executeHookForEveryInjectable(hookName: hookName) {
const injectables = this.injector.getAll();
for (const [_, value] of injectables) {
const instance: unknown = value();
await this.executeInstanceHook(instance, hookName);
const instanceOrValue: unknown = value();
if (!MetadataHelper.IsObject(instanceOrValue)) {
continue;
}
await this.executeInstanceHook(instanceOrValue, hookName);
}
}

Expand Down
15 changes: 9 additions & 6 deletions src/injector/injector.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
import { Logger } from '../logger.ts';
import { MetadataHelper } from '../metadata/helper.ts';
import { ModuleInstance } from '../module/decorator.ts';
import { ControllerConstructor } from '../router/controller/constructor.ts';
import { Constructor } from '../utils/constructor.ts';
import { getInjectionTokenMetadataKey } from './decorator.ts';
Expand All @@ -15,6 +14,7 @@ import {
SCOPE,
} from './injectable/decorator.ts';
import { ExecutionContext } from '../router/router.ts';
import { ModuleMetadata } from '../mod.ts';

export class Injector {
private resolved = new Map<
Expand All @@ -31,7 +31,7 @@ export class Injector {
string,
Map<Constructor | string, unknown>
>();
public modules: Array<ModuleInstance> = [];
public modules: Array<any> = [];
// deno-lint-ignore no-explicit-any
public controllers: Array<any> = [];
// deno-lint-ignore no-explicit-any
Expand All @@ -52,7 +52,7 @@ export class Injector {
throw Error(`Type ${Type} not injected`);
}

public async bootstrapModule(module: ModuleInstance) {
public async bootstrapModule(module: ModuleMetadata) {
this.logger.log(`Bootstraping ${module.constructor.name}`);
const { controllers, injectables } = module;
if (injectables) {
Expand Down Expand Up @@ -166,12 +166,15 @@ export class Injector {
if (canBeSingleton) {
for (const [idx, Dep] of parameters.entries()) {
const token = this.getParamToken(actualType, idx);
const type = this.resolvedTypes.get(token ?? Dep);
const typeOrValue = this.resolvedTypes.get(token ?? Dep);
if (!MetadataHelper.IsObject(typeOrValue)) {
continue;
}
const dependencyInjectableMetadata = MetadataHelper.getMetadata<
InjectableOption
>(
injectionData,
type,
typeOrValue,
);
if (
dependencyInjectableMetadata?.scope === SCOPE.REQUEST ||
Expand Down Expand Up @@ -230,7 +233,7 @@ export class Injector {
if (Object.hasOwn(Type, 'useClass')) {
return { actualKey, actualType: (Type as UseClassInjector).useClass };
} else if (Object.hasOwn(Type, 'useValue')) {
return { actualKey, instance: (Type as UseValueInjector).useValue };
return { actualKey, instance: (Type as UseValueInjector).useValue ?? null};
}
}
return {
Expand Down
2 changes: 2 additions & 0 deletions src/kv-queue/constants.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
export const queueListenerMetadataKey = 'queue-listener';

export const KV_QUEUE_NAME = 'KV_QUEUE_NAME';

// deno-lint-ignore no-explicit-any
export type QueueEvent<T = any> = {
type: string;
Expand Down
22 changes: 14 additions & 8 deletions src/kv-queue/kv.ts
Original file line number Diff line number Diff line change
@@ -1,17 +1,25 @@
import { OnAppClose } from '../hook/mod.ts';
import { OnAppBootstrap, OnAppClose } from '../hook/mod.ts';
import { Inject } from '../mod.ts';
import { Injectable } from '../mod.ts';
import { QueueEvent } from './constants.ts';
import { KV_QUEUE_NAME, QueueEvent } from './constants.ts';

// deno-lint-ignore no-explicit-any
type Listener<P = any> = (payload: P) => void;

@Injectable()
export class KvQueue implements OnAppClose {
export class KvQueue implements OnAppClose, OnAppBootstrap {
private kv!: Deno.Kv;
private listenersMap: Map<string, Listener> = new Map();

public async start(name?: string) {
this.kv = await Deno.openKv(name);
constructor(@Inject(KV_QUEUE_NAME) private name: string) {
}

public async onAppClose(): Promise<void> {
await this.kv.close();
}

public async onAppBootstrap(): Promise<void> {
this.kv = await Deno.openKv(this.name);
}

public sendMessage(type: string, data: unknown) {
Expand All @@ -33,7 +41,5 @@ export class KvQueue implements OnAppClose {
});
}

public async onAppClose(): Promise<void> {
await this.kv.close();
}

}
20 changes: 11 additions & 9 deletions src/kv-queue/module.ts
Original file line number Diff line number Diff line change
@@ -1,26 +1,28 @@
import { OnAppBootstrap, OnAppClose } from '../hook/interfaces.ts';
import { MetadataHelper } from '../metadata/helper.ts';
import { injector, Logger, Module } from '../mod.ts';
import { queueListenerMetadataKey } from './constants.ts';
import { KV_QUEUE_NAME, queueListenerMetadataKey } from './constants.ts';
import { KvQueue } from './kv.ts';

@Module({})
export class KvQueueModule implements OnAppBootstrap {
private logger: Logger = new Logger('QueueModule');

public injectables = [KvQueue];

constructor(private kvName?: string) {}
constructor() {}

public static forRoot(kvName?: string) {
return new KvQueueModule(kvName);
return {
injectables: [{ token: KV_QUEUE_NAME, useValue: kvName }, KvQueue],
module: KvQueueModule,
}
}

async onAppBootstrap(): Promise<void> {
const queue = injector.get<KvQueue>(KvQueue);
await queue.start(this.kvName);
for (const instance of injector.injectables) {
this.registerAvailableEventListeners(instance);
for (const instanceOrPlainValue of injector.injectables) {
if (!MetadataHelper.IsObject(instanceOrPlainValue)) {
continue;
}
this.registerAvailableEventListeners(instanceOrPlainValue);
}
}

Expand Down
7 changes: 7 additions & 0 deletions src/metadata/helper.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,13 @@
import { Reflect } from '../deps.ts';

export class MetadataHelper {

static IsObject<T>(
x: T | undefined | null | boolean | string | symbol | number,
): x is T {
return typeof x === "object" ? x !== null : typeof x === "function";
}

static getMetadata<T>(
key: string,
obj: unknown,
Expand Down
14 changes: 8 additions & 6 deletions src/module/decorator.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,19 +5,21 @@ import { Constructor } from '../utils/constructor.ts';
import { ModuleConstructor } from './constructor.ts';
import { UseClassInjector, UseValueInjector } from '../mod.ts';

export class ModuleOptions {
imports?: Array<ModuleConstructor | ModuleOptions> = [];
controllers?: ControllerConstructor[] = [];
export interface ModuleMetadata {
imports?: Array<ModuleConstructor | DynamicModule>;
controllers?: ControllerConstructor[];
injectables?: Array<
InjectableConstructor | UseValueInjector | UseClassInjector
> = [];
>;
}

export class ModuleInstance extends ModuleOptions {}
export interface DynamicModule extends ModuleMetadata {
module: ModuleConstructor;
}

export const moduleMetadataKey = 'module';

export function Module<T>(options: ModuleOptions) {
export function Module<T>(options: ModuleMetadata) {
return (Type: Constructor<T>): void => {
MetadataHelper.setMetadata(moduleMetadataKey, options, Type);
};
Expand Down

0 comments on commit 9468a0e

Please sign in to comment.