Skip to content

Commit

Permalink
cycle error
Browse files Browse the repository at this point in the history
  • Loading branch information
zheksoon committed Mar 24, 2024
1 parent 3b36864 commit 13a5707
Show file tree
Hide file tree
Showing 2 changed files with 260 additions and 1 deletion.
234 changes: 234 additions & 0 deletions src/__tests__/dioma.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -196,4 +196,238 @@ describe("Dioma", () => {
expect(instanceA2.instanceC).not.toBe(instanceA.instanceC);
});
});

describe("Complex dependency", () => {
it("should be able to inject complex dependency", () => {
class DependencyA {
constructor(public instanceB = inject(DependencyB)) {}

static scope = Scopes.Transient();
}

class DependencyB {
constructor(public instanceC = inject(DependencyC)) {}

static scope = Scopes.Transient();
}

class DependencyC {
constructor(public instanceD = inject(DependencyD)) {}

static scope = Scopes.Transient();
}

class DependencyD {
constructor() {}

static scope = Scopes.Transient();
}

const instanceA = inject(DependencyA);

expect(instanceA).toBeInstanceOf(DependencyA);

expect(instanceA.instanceB).toBeInstanceOf(DependencyB);

expect(instanceA.instanceB.instanceC).toBeInstanceOf(DependencyC);

expect(instanceA.instanceB.instanceC.instanceD).toBeInstanceOf(DependencyD);
});

it("should be able to inject complex dependency with resolution scope", () => {
class DependencyZ {
constructor(
public instanceA = inject(DependencyA),
public instanceB = inject(DependencyB)
) {}

static scope = Scopes.Transient();
}

class DependencyA {
constructor(
public instanceB = inject(DependencyB),
public instanceC = inject(DependencyC)
) {}

static scope = Scopes.Transient();
}

class DependencyB {
constructor(public instanceC = inject(DependencyC)) {}

static scope = Scopes.Transient();
}

class DependencyC {
constructor() {}

static scope = Scopes.Resolution();
}

const instanceZ = inject(DependencyZ);

expect(instanceZ).toBeInstanceOf(DependencyZ);

expect(instanceZ.instanceA).toBeInstanceOf(DependencyA);

expect(instanceZ.instanceB).toBeInstanceOf(DependencyB);

expect(instanceZ.instanceA.instanceB).toBeInstanceOf(DependencyB);

expect(instanceZ.instanceA.instanceC).toBeInstanceOf(DependencyC);

expect(instanceZ.instanceB.instanceC).toBeInstanceOf(DependencyC);

expect(instanceZ.instanceA.instanceC).toBe(instanceZ.instanceB.instanceC);
});
});

describe("Circular dependency", () => {
it("should throw error when circular dependency is detected for transient scope", () => {
class CircularDependencyA {
constructor(public instanceB = inject(CircularDependencyB)) {}

static scope = Scopes.Transient();
}

class CircularDependencyB {
constructor(public instanceA = inject(CircularDependencyA)) {}

static scope = Scopes.Transient();
}

expect(() => inject(CircularDependencyA)).toThrowError("Circular dependency detected");
});

it("should throw error when circular dependency is detected for container scope", () => {
const container = new Container();

class CircularDependencyA {
constructor(public instanceB = container.inject(CircularDependencyB)) {}

static scope = Scopes.Scoped();
}

class CircularDependencyB {
constructor(public instanceA = container.inject(CircularDependencyA)) {}

static scope = Scopes.Scoped();
}

expect(() => container.inject(CircularDependencyA)).toThrowError(
"Circular dependency detected"
);
});

it("should throw error when circular dependency is detected for resolution scope", () => {
class CircularDependencyA {
constructor(public instanceB = inject(CircularDependencyB)) {}

static scope = Scopes.Resolution();
}

class CircularDependencyB {
constructor(public instanceA = inject(CircularDependencyA)) {}

static scope = Scopes.Resolution();
}

const container = new Container();

expect(() => container.inject(CircularDependencyA)).toThrowError(
"Circular dependency detected"
);
});

it("should throw error when circular dependency is detected for singleton scope", () => {
class CircularDependencyA {
constructor(public instanceB = inject(CircularDependencyB)) {}

static scope = Scopes.Singleton();
}

class CircularDependencyB {
constructor(public instanceA = inject(CircularDependencyA)) {}

static scope = Scopes.Singleton();
}

expect(() => inject(CircularDependencyA)).toThrowError("Circular dependency detected");
});

it("should throw error when circular dependency is detected for multiple classes", () => {
class CircularDependencyA {
constructor(public instanceB = inject(CircularDependencyB)) {}

static scope = Scopes.Transient();
}

class CircularDependencyB {
constructor(public instanceC = inject(CircularDependencyC)) {}

static scope = Scopes.Transient();
}

class CircularDependencyC {
constructor(public instanceA = inject(CircularDependencyA)) {}

static scope = Scopes.Transient();
}

expect(() => inject(CircularDependencyA)).toThrowError("Circular dependency detected");

expect(() => inject(CircularDependencyB)).toThrowError("Circular dependency detected");

expect(() => inject(CircularDependencyC)).toThrowError("Circular dependency detected");
});
});

describe.skip("Async", () => {
it("should be able to inject async", async () => {
class AsyncClass {
static scope = Scopes.Transient();
}

const instance = await inject.async(AsyncClass);

expect(instance).toBeInstanceOf(AsyncClass);
});

it("should be able to break circular dependency with async", { timeout: 1000 }, async () => {
class CircularDependencyA {
constructor(public instanceB = inject(CircularDependencyB)) {}

static scope = Scopes.Transient();
}

class CircularDependencyB {
declare instanceA: CircularDependencyA;

constructor(instanceAPromise = inject.async(CircularDependencyA)) {
instanceAPromise.then((instance) => {
this.instanceA = instance;
});
}

static scope = Scopes.Transient();
}

const instance = inject(CircularDependencyA);

await Promise.resolve();

expect(instance).toBeInstanceOf(CircularDependencyA);

expect(instance.instanceB).toBeInstanceOf(CircularDependencyB);

// const instance2 = inject(CircularDependencyB);

// await Promise.resolve();

// expect(instance2).toBeInstanceOf(CircularDependencyB);

// expect(instance2.instanceA).toBeInstanceOf(CircularDependencyA);
});
});
});
27 changes: 26 additions & 1 deletion src/dioma.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,10 +10,19 @@ interface ScopedClass {

export type Injectable<C extends I, I extends ScopedClass = ScopedClass> = InstanceType<I>;

class CycleDependencyError extends Error {
constructor() {
super("Circular dependency detected");
}
}

export class Container {
private instances = new WeakMap();

private resolutionContainer: Container | null = null;

private resolutionSet = new Set();

constructor(private parentContainer: Container | null = null) {}

childContainer = () => {
Expand Down Expand Up @@ -43,15 +52,31 @@ export class Container {

const scope = cls.scope || Scopes.Transient();

if (this.resolutionSet.has(cls)) {
throw new CycleDependencyError();
}

this.resolutionSet.add(cls);

let instance: InstanceType<T> | undefined;

try {
return scope(cls, this, this.resolutionContainer);
instance = scope(cls, this, this.resolutionContainer);

return instance!;
} finally {
this.resolutionSet.delete(cls);

this.resolutionContainer = oldResolutionContainer;
}
};

register = this.inject;

reset = () => {
this.instances = new WeakMap();
};

static Scopes = class Scopes {
public static Singleton(): Scope {
return function SingletonScope(cls) {
Expand Down

0 comments on commit 13a5707

Please sign in to comment.