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

[SUGGESTION] Generic Service class usable with cds-typer #242

Open
aschmidt93 opened this issue Sep 18, 2024 · 0 comments
Open

[SUGGESTION] Generic Service class usable with cds-typer #242

aschmidt93 opened this issue Sep 18, 2024 · 0 comments

Comments

@aschmidt93
Copy link

aschmidt93 commented Sep 18, 2024

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";
  };

  type ServiceDefinition = { [key: string]: ActionFunctionDef | unknown };

  type EventDef = new () => Record<string, any>;

  type IsEntity<T> = T extends { new (...args: any[]): any }
    ? "actions" extends keyof T
      ? T["actions"] extends Record<any, any>
        ? true
        : false
      : false
    : false;

  type IsEntitySet<T> = T extends { new (...args: any[]): Array<infer U> } ? true : false;

  type ServiceEvents<T extends ServiceDefinition> = {
    [key in keyof T as IsEntity<T[key]> extends false
      ? IsEntitySet<T[key]> extends false
        ? T[key] extends EventDef
          ? key
          : never
        : never
      : never]: T[key] extends new () => infer U ? 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>;
    };
  }
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

1 participant