Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Update container with unbind all #285

Merged
merged 3 commits into from
Jan 11, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .changeset/dull-books-cover.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@inversifyjs/core": minor
---

Updated `BindingService` with `getNonParentBoundServices`
5 changes: 5 additions & 0 deletions .changeset/fluffy-ravens-heal.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@inversifyjs/container": minor
---

Updated `Container` with `unbindAll`
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,7 @@ describe(Container.name, () => {
bindingServiceMock = {
clone: jest.fn(),
get: jest.fn(),
getNonParentBoundServices: jest.fn(),
removeAllByModuleId: jest.fn(),
removeAllByServiceId: jest.fn(),
set: jest.fn(),
Expand Down Expand Up @@ -1153,6 +1154,75 @@ describe(Container.name, () => {
});
});

describe('.unbindAll', () => {
describe('when called', () => {
let serviceIdsFixture: string[];
let result: unknown;

beforeAll(async () => {
serviceIdsFixture = ['service1', 'service2'];
bindingServiceMock.getNonParentBoundServices.mockReturnValueOnce(
serviceIdsFixture,
);

result = await new Container().unbindAll();
});

afterAll(() => {
jest.clearAllMocks();
});

it('should call resolveServiceDeactivations for each service', () => {
expect(resolveServiceDeactivations).toHaveBeenCalledTimes(
serviceIdsFixture.length,
);
for (const serviceId of serviceIdsFixture) {
expect(resolveServiceDeactivations).toHaveBeenCalledWith(
expect.any(Object),
serviceId,
);
}
});

it('should call removeAllByServiceId on activationService for each service', () => {
expect(
activationServiceMock.removeAllByServiceId,
).toHaveBeenCalledTimes(serviceIdsFixture.length);
for (const serviceId of serviceIdsFixture) {
expect(
activationServiceMock.removeAllByServiceId,
).toHaveBeenCalledWith(serviceId);
}
});

it('should call removeAllByServiceId on bindingService for each service', () => {
expect(bindingServiceMock.removeAllByServiceId).toHaveBeenCalledTimes(
serviceIdsFixture.length,
);
for (const serviceId of serviceIdsFixture) {
expect(bindingServiceMock.removeAllByServiceId).toHaveBeenCalledWith(
serviceId,
);
}
});

it('should call removeAllByServiceId on deactivationService for each service', () => {
expect(
deactivationServiceMock.removeAllByServiceId,
).toHaveBeenCalledTimes(serviceIdsFixture.length);
for (const serviceId of serviceIdsFixture) {
expect(
deactivationServiceMock.removeAllByServiceId,
).toHaveBeenCalledWith(serviceId);
}
});

it('should return undefined', () => {
expect(result).toBeUndefined();
});
});
});

describe('.unload', () => {
let containerModuleFixture: ContainerModule;

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -270,6 +270,36 @@ export class Container {
this.#deactivationService.removeAllByServiceId(serviceIdentifier);
}

public async unbindAll(): Promise<void> {
const deactivationParams: DeactivationParams =
this.#buildDeactivationParams();

const nonParentBoundServiceIds: ServiceIdentifier[] = [
...this.#bindingService.getNonParentBoundServices(),
];

await Promise.all(
nonParentBoundServiceIds.map(
async (serviceId: ServiceIdentifier): Promise<void> =>
resolveServiceDeactivations(deactivationParams, serviceId),
),
);

/*
* Removing service related objects here so unload is deterministic.
*
* Removing service related objects as soon as resolveModuleDeactivations takes
* effect leads to module deactivations not triggering previously deleted
* deactivations, introducing non determinism depending in the order in which
* services are deactivated.
*/
for (const serviceId of nonParentBoundServiceIds) {
this.#activationService.removeAllByServiceId(serviceId);
this.#bindingService.removeAllByServiceId(serviceId);
this.#deactivationService.removeAllByServiceId(serviceId);
}
}

public async unload(...modules: ContainerModule[]): Promise<void> {
const deactivationParams: DeactivationParams =
this.#buildDeactivationParams();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -209,6 +209,35 @@ describe(BindingService.name, () => {
});
});

describe('.getNonParentBoundServices', () => {
describe('when called', () => {
let serviceIdsFixture: ServiceIdentifier[];

let result: Iterable<ServiceIdentifier>;

beforeAll(() => {
serviceIdsFixture = ['service-id-1', 'service-id-2'];

bindingMapsMock.getAllKeys.mockReturnValueOnce(serviceIdsFixture);

result = bindingService.getNonParentBoundServices();
});

afterAll(() => {
jest.clearAllMocks();
});

it('should return the non-parent bound services', () => {
expect(result).toStrictEqual(serviceIdsFixture);
});

it('should call bindingMaps.getAllKeys()', () => {
expect(bindingMapsMock.getAllKeys).toHaveBeenCalledTimes(1);
expect(bindingMapsMock.getAllKeys).toHaveBeenCalledWith('serviceId');
});
});
});

describe('.removeAllByModuleId', () => {
let moduleIdFixture: number;

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,10 @@ export class BindingService implements Cloneable<BindingService> {
);
}

public getNonParentBoundServices(): Iterable<ServiceIdentifier> {
return this.#bindingMaps.getAllKeys(BindingRelationKind.serviceId);
}

public getByModuleId<TResolved>(
moduleId: number,
): Iterable<Binding<TResolved>> | undefined {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -127,6 +127,46 @@ describe(OneToManyMapStar.name, () => {
});
});

describe('.getAllKeys', () => {
describe('having a OneToManyMapStart with a single model', () => {
let modelFixture: unknown;
let relationFixture: Required<RelationTest>;
let relationKeyFixture: RelationKey.foo;
let oneToManyMapStar: OneToManyMapStar<unknown, RelationTest>;

beforeAll(() => {
modelFixture = Symbol();
relationFixture = {
bar: 3,
foo: 'foo',
};
relationKeyFixture = RelationKey.foo;
oneToManyMapStar = new OneToManyMapStar<unknown, RelationTest>({
bar: {
isOptional: true,
},
foo: {
isOptional: false,
},
});

oneToManyMapStar.set(modelFixture, relationFixture);
});

describe('when called', () => {
let result: unknown;

beforeAll(() => {
result = [...oneToManyMapStar.getAllKeys(relationKeyFixture)];
});

it('should return expected result', () => {
expect(result).toStrictEqual([relationFixture[relationKeyFixture]]);
});
});
});
});

describe('.removeByRelation', () => {
describe('having a OneToManyMapStart with a no models', () => {
let relationFixture: Required<RelationTest>;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,12 @@ export class OneToManyMapStar<TModel, TRelation extends object>
return this.#relationToModelsMaps[key].get(value)?.values();
}

public getAllKeys<TKey extends keyof TRelation>(
key: TKey,
): Iterable<TRelation[TKey]> {
return this.#relationToModelsMaps[key].keys();
}

public removeByRelation<TKey extends keyof TRelation>(
key: TKey,
value: Required<TRelation>[TKey],
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@ const cloneMock: jest.Mock<any> = jest.fn();
// eslint-disable-next-line @typescript-eslint/no-explicit-any
const getMock: jest.Mock<any> = jest.fn();
// eslint-disable-next-line @typescript-eslint/no-explicit-any
const getAllKeysMock: jest.Mock<any> = jest.fn();
// eslint-disable-next-line @typescript-eslint/no-explicit-any
const removeByRelationMock: jest.Mock<any> = jest.fn();
// eslint-disable-next-line @typescript-eslint/no-explicit-any
const setMock: jest.Mock<any> = jest.fn();
Expand All @@ -16,6 +18,10 @@ export class OneToManyMapStar<TModel, TRelation extends object> {
value: Required<TRelation>[TKey],
) => Iterable<TModel> | undefined;

public readonly getAllKeys: <TKey extends keyof TRelation>(
key: TKey,
) => Iterable<TRelation[TKey]>;

public readonly removeByRelation: <TKey extends keyof TRelation>(
key: TKey,
value: Required<TRelation>[TKey],
Expand All @@ -26,6 +32,7 @@ export class OneToManyMapStar<TModel, TRelation extends object> {
constructor() {
this.clone = cloneMock;
this.get = getMock;
this.getAllKeys = getAllKeysMock;
this.removeByRelation = removeByRelationMock;
this.set = setMock;
}
Expand Down
Loading