Closed marceliwac closed 6 months ago
@icebob , Apologies for any confusion!
I updated the test/typescript/hello-world/greeter.service.ts (i.e. service schema) to the one used in the typescript template repository, since I couldn't find any in-code uses of the hooks or any tests related to that schema. I must be missing something, most likely the actual test that use this code. Could you please point me to the tests you're referring to?
I tried running all the tests specified in package.json which all pass fine (with the exception of memory leak tests, which I am guessing are unrelated).
I've checked again the code and it looks the test can be fine, but you removed a lot of things from the original greeter service, e.g. the hooks which uppercaseing the context params. it would be good if you keep the same logic in the service file.
If you should modify the test files, it means the changes contains breaking changes.
@icebob You're absolutely right, sorry for that!
I reverted the deletions and tweaked them to comply with the service definition. I also added the implementation for the uppercase()
method (which was declared as existing on the service but didn't) and replaced the direct call to toUpperCase()
to a call to that method. Finally, I added the missing type delcaration for this
where appropriate.
Great, thanks!
@marceliwac I've just updated moleculer versions and I'm encountering type errors as a result of this change.
Can you clarify why the ServiceSchema
generic has a default of void
while the lifecycle handlers have a default of Service<S>
? This causes a conflict in scenarios where both types are being used along with their defaults. Is there a reason why ServiceSchema
didn't have the same default as the lifecycle handlers?
@shawnmcknight
Apologies for the late reply and thanks for bringing this up!
You're right, these should in fact use Service<S>
rather than void
. The S
type also seems to be redundant:
type ServiceSyncLifecycleHandler<T> = (this: T) => void;
type ServiceAsyncLifecycleHandler<T> = (this: T) => void | Promise<void>;
interface ServiceSchema<S = ServiceSettingSchema, T = Service<S>> {
name: string;
version?: string | number;
settings?: S;
dependencies?: string | ServiceDependency | (string | ServiceDependency)[];
metadata?: any;
actions?: ServiceActionsSchema;
mixins?: Partial<ServiceSchema>[];
methods?: ServiceMethods;
hooks?: ServiceHooks;
events?: ServiceEvents;
created?: ServiceSyncLifecycleHandler<T> | ServiceSyncLifecycleHandler<T>[];
started?: ServiceAsyncLifecycleHandler<T> | ServiceAsyncLifecycleHandler<T>[];
stopped?: ServiceAsyncLifecycleHandler<T> | ServiceAsyncLifecycleHandler<T>[];
[name: string]: any;
}
Ideally, if lifecycle handlers were nested within a separate property there would be a much cleaner solution that uses ThisType<T>
utility like so:
type ServiceSyncLifecycleHandler = () => void;
type ServiceAsyncLifecycleHandler = () => void | Promise<void>;
type ServiceLifecycle<T> = {
created?: ServiceSyncLifecycleHandler | ServiceSyncLifecycleHandler[];
started?: ServiceAsyncLifecycleHandler | ServiceAsyncLifecycleHandler[];
stopped?: ServiceAsyncLifecycleHandler | ServiceAsyncLifecycleHandler[];
};
interface ServiceSchema<S = ServiceSettingSchema, T = Service<S>> {
name: string;
version?: string | number;
settings?: S;
dependencies?: string | ServiceDependency | (string | ServiceDependency)[];
metadata?: any;
actions?: ServiceActionsSchema;
mixins?: Partial<ServiceSchema>[];
methods?: ServiceMethods;
hooks?: ServiceHooks;
events?: ServiceEvents;
lifecycle?: ServiceLifecycle & ThisType<T>;
[name: string]: any;
}
But because the lifecycle handlers are direct properties of the service, this makes things slightly messier. ServiceSchema
is an interface and extending it with a type of LifecycleHandlers<T>
(which is legal in TS >= 2.2) doesn't seem to apply the ThisType<T>
correctly:
type ServiceSyncLifecycleHandler = () => void;
type ServiceAsyncLifecycleHandler = () => void | Promise<void>;
type ServiceLifecycle<T> = {
created?: ServiceSyncLifecycleHandler | ServiceSyncLifecycleHandler[];
started?: ServiceAsyncLifecycleHandler | ServiceAsyncLifecycleHandler[];
stopped?: ServiceAsyncLifecycleHandler | ServiceAsyncLifecycleHandler[];
} & ThisType<T>;
interface ServiceSchema<S = ServiceSettingSchema, T = Service<S>> extends ServiceLifecycle<T> {
name: string;
version?: string | number;
settings?: S;
dependencies?: string | ServiceDependency | (string | ServiceDependency)[];
metadata?: any;
actions?: ServiceActionsSchema;
mixins?: Partial<ServiceSchema>[];
methods?: ServiceMethods;
hooks?: ServiceHooks;
// `created`, `started` and `stopped` are defined, but don't have the correct type for `this`
events?: ServiceEvents;
[name: string]: any;
}
On a side note, I also think that the generic T
should be passed to the methods, events and handlers so we don't have to explicitly set this
in each of the handlers. That matters less, because it still needs to be done if the function is defined outside of schema definition.
@icebob Any thoughts on this? Should I submit a PR?
I have no idea due to lack of TS experience. @shawnmcknight ?
what @marceliwac suggested would resolve our issues with the current version
@shawnmcknight, @icebob, @MichaelErmer I've pushed the PR to address this. I would appreciate a second pair of eyes sanity checking it if you could find some time. Hopefully, the added TS tests should prevent similar issues in the future.
I'm not 100% sure how to verify that this
type of the lifecycle handlers (created
, started
and stopped
) actually poitns to the instance of Service
type, hence the use surrogate types (ServiceSyncLifecycleHandler
and ServiceAsyncLifecycleHandler
) in the tests. If you have better ideas, I'm open to suggestions.
This change expands the typings for the ServiceSchema, allowing it to accept the "service-this" type used by the lifecycle handlers in the typescript template. Please see this issue for a more complete explanation.
:memo: Description
Change the type of the ServiceSchema to accept two generic types, latter of which is new and used to provide types for the lifecycle methods (created, started, stopped).
The typescript template will require a minor change if this change is accepted.
:dart: Relevant issues
moleculer-template-project-typescript #70
:gem: Type of change
:vertical_traffic_light: How Has This Been Tested?
The example code to verify that this change works is outlined in moleculer-template-project-typescript #70.
:checkered_flag: Checklist: