-
Notifications
You must be signed in to change notification settings - Fork 15
Description
The Service
class is currently not generic but has some generic methods, e.g. emit
:
// [email protected]
export class Service extends QueryAPI {
emit: {
<T = any>(details: { event: types.event, data?: object, headers?: object }): Promise<T>,
<T = any>(event: types.event, data?: object, headers?: object): Promise<T>,
}
}
This is cumbersome to use, as users have to manually provide the types and there are no compile time checks.
With the output of cds-typer
many properties of the Service
class can be fully typed. Therefore, I suggest making the Service
class generic where the generic parameter is the output of cds-typer
for a service. Alternatively, define a new TypedService
type if no changes shall be made to the Service
class. This concept is already used for adding handlers to actions and functions of a service.
export class Service<T extends ServiceDefinition = any> extends QueryAPI { ...}
The type ServiceDefinition
shall describe the output of cds-typer
for a service, much like CdsFunction
describes the output of cds-typer
for a service operation
export type CdsFunction = {
(...args: any[]): any,
__parameters: object,
__returns: any,
}
Example usage:
// order-service.cds
service OrderService {
event orderCanceled {
orderID : String;
}
action cancelOrder(orderID : String);
}
import type * as OrderServiceTypes from "@cds-models/OrderService"
const orderService : Service<typeof OrderServiceTypes> = cds.services["OrderService"];
// Error: param `orderID` missing
await orderService.emit("orderCanceled", {});
// auto-completion and compile time checks
await orderService.cancelOrder({orderID : "1"});
await orderService.send("cancelOrder", {orderID : "2"});
In my projects I usually define the TypedService
class which looks something like this for the emit method. This is far from perfect and can and should be improved.
// the `kind` property is currently missing in `CdsFunction`
type ActionFunctionDef = CdsFunction & {
kind: "action" | "function";
};
/**
* Definition of a service as generated by cds-typer
*/
export type ServiceDefinition = {
[key: string]:
| ActionFunctionDef
| EventDef
| EntityDefinition
| EntitySetDefinition<any>
| unknown;
};
type ActionFunctionDef = {
__returns: any;
__parameters: any;
(...args: any): any;
kind: "action" | "function";
};
export type ServiceActionsFunctions<T extends ServiceDefinition> = {
[key in keyof T as T[key] extends ActionFunctionDef
? key
: never]: T[key] extends ActionFunctionDef ? T[key] : never;
};
type EventDef = new () => Record<string, any>;
/**
* Definition of a single entity (row in a table) as generated by cds-typer
*/
export type EntityDefinition = { new (...args: any[]): any; readonly actions: Record<any, any> };
/**
* Definition of an entity set as generated by cds-typer
*/
export type EntitySetDefinition<T extends InstanceType<EntityDefinition>> = {
new (...args: any[]): Array<T>;
};
/**
* Resolves to `true` if `T` is an EventDef
*/
type IsEventDef<T> = T extends EntityDefinition
? false
: T extends EntitySetDefinition<infer _>
? false
: T extends EventDef
? true
: false;
/**
* Available events in a service
*/
export type ServiceEvents<T extends ServiceDefinition> = {
[key in keyof T as IsEventDef<T[key]> extends true
? key
: never]: T[key] extends new () => infer U ? U : never;
};
/**
* Available entity sets of a service
*/
export type ServiceEntitySets<T extends ServiceDefinition> = {
[K in keyof T as T[K] extends EntitySetDefinition<infer U>
? K
: never]: T[K] extends EntitySetDefinition<infer U> ? EntitySetDefinition<U> : never;
};
// module augmentation
class ApplicationService<T extends ServiceDefinition> extends Service {
emit: {
<E extends keyof ServiceEvents<T>>(details: {
event: E;
data?: ServiceEvents<T>[E];
headers?: object;
}): Promise<T>;
<E extends keyof ServiceEvents<T>>(
event: E,
data?: ServiceEvents<T>[E],
headers?: object
): Promise<T>;
};
}