Skip to content

Make ESService support static props #193

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

Merged
merged 13 commits into from
Mar 19, 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
18 changes: 18 additions & 0 deletions .changeset/metal-windows-run.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
---
'@freshgum/typedi': minor
---

The `ESService` decorator now supports classes with static properties.
This was an oversight in the original design of the decorator's signature.

This change affects the type parameters consumed by `ESService`; the main difference
being that there is now a second parameter, `TClass`, which directly pertains to the
type of the class being decorated (and thus, the type returned by the decorator.)

Therefore, the type parameters of the function have changed from [`T = unknown`][esservice-old-type-params]
to [`TInstance = unknown, TClass extends Constructable<TInstance> = Constructable<TInstance>`][esservice-new-type-params].

Current code which relies on the type signature of `ESService` will not be affected.

[esservice-old-type-params]: https://github.com/freshgum-bubbles/typedi/blob/4c76133d3a94e119d5b4d44846213df42d3010a5/src/contrib/es/es-service.decorator.mts#L38
[esservice-new-type-params]: https://github.com/freshgum-bubbles/typedi/blob/a3825b77fadf6143f282e5cf4b68c084076b8369/src/contrib/es/es-service.decorator.mts#L38
29 changes: 17 additions & 12 deletions src/contrib/es/es-service.decorator.mts
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,9 @@ import { ESClassDecorator } from '../util/es-class-decorator.type.mjs';
*
* @returns A decorator which is then used upon a class.
*/
export function ESService<T = unknown>(dependencies: AnyServiceDependency[]): ESClassDecorator<Constructable<T>>;
export function ESService<TInstance = unknown, TClass extends Constructable<TInstance> = Constructable<TInstance>>(
dependencies: AnyServiceDependency[]
): ESClassDecorator<TClass>;

/**
* Marks class as a service that can be injected using Container.
Expand Down Expand Up @@ -74,10 +76,10 @@ export function ESService<T = unknown>(dependencies: AnyServiceDependency[]): ES
*
* @returns A decorator which is then used upon a class.
*/
export function ESService<T = unknown>(
options: Omit<ServiceOptions<T>, 'dependencies'>,
export function ESService<TInstance = unknown, TClass extends Constructable<TInstance> = Constructable<TInstance>>(
options: Omit<ServiceOptions<TInstance>, 'dependencies'>,
dependencies: AnyServiceDependency[]
): ESClassDecorator<Constructable<T>>;
): ESClassDecorator<TClass>;

/**
* Marks class as a service that can be injected using Container.
Expand Down Expand Up @@ -115,22 +117,25 @@ export function ESService<T = unknown>(
*
* @returns A decorator which is then used upon a class.
*/
export function ESService<T = unknown>(
options: ServiceOptionsWithDependencies<Constructable<unknown>>
): ESClassDecorator<Constructable<T>>;
export function ESService<TInstance = unknown, TClass extends Constructable<TInstance> = Constructable<TInstance>>(
options: ServiceOptionsWithDependencies<TInstance>
): ESClassDecorator<TClass>;

export function ESService<T = unknown>(
optionsOrDependencies: Omit<ServiceOptions<T>, 'dependencies'> | ServiceOptions<T> | AnyServiceDependency[],
export function ESService<TInstance = unknown, TClass extends Constructable<TInstance> = Constructable<TInstance>>(
optionsOrDependencies:
| Omit<ServiceOptions<TInstance>, 'dependencies'>
| ServiceOptions<TInstance>
| AnyServiceDependency[],
maybeDependencies?: AnyServiceDependency[]
): ESClassDecorator<Constructable<T>> {
return (target: Constructable<T>, context: ClassDecoratorContext) => {
): ESClassDecorator<TClass> {
return (target: TClass, context: ClassDecoratorContext) => {
// This is probably overcautious.
if (context.kind !== 'class') {
throw new Error('@ESService() must only be used to decorate classes.');
}

Service(
optionsOrDependencies as ServiceOptionsWithoutDependencies<T>,
optionsOrDependencies as ServiceOptionsWithoutDependencies<TClass>,
maybeDependencies as AnyServiceDependency[]
)(target);
};
Expand Down
2 changes: 1 addition & 1 deletion src/contrib/upstream/tree
Submodule tree updated from 9bab80 to 0edde7
18 changes: 18 additions & 0 deletions test/contrib/es/es-service.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,4 +18,22 @@ describe('ESService', () => {
const context = createFakeClassDecoratorContext(MyService);
expect(() => ESService([])(class {}, context)).not.toThrow();
});

it('should support static properties and methods', function () {
class ClassWithStaticMethodsAndProperties {
static property = true;

static get getter() {
return 'value';
}

static method() {}
}

const context = createFakeClassDecoratorContext(ClassWithStaticMethodsAndProperties);

// We don't need an expect(...) here as this is a typing issue, not a runtime one.
ESService([])(ClassWithStaticMethodsAndProperties, context);
expect(true).toBe(true);
});
});
1 change: 1 addition & 0 deletions test/decorators/Service.spec.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
/* eslint-disable @typescript-eslint/no-unused-vars */
import { Container, ContainerInstance, Service, Token } from 'internal:typedi';
import { WrappedESServiceDecorator } from '../contrib/es/test-utils/es-service-decorator-wrapper.util';
import { ESService } from 'internal:typedi/contrib/es/es-service.decorator.mjs';

// To ensure conformance between different Service implementations,
// we wrap some decorators here with stubs which pass them quasi-values.
Expand Down
Loading