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

Decorators #83

Closed
stazz opened this issue May 24, 2023 · 3 comments
Closed

Decorators #83

stazz opened this issue May 24, 2023 · 3 comments
Assignees

Comments

@stazz
Copy link
Member

stazz commented May 24, 2023

It would be great to have something like this:

// app.ts
import * as tyras from "@ty-ras/...";
export default tyras.createAppBuilder<...>();
// my-endpoint.ts
import * as tyras from "@ty-ras/...";
import app from "./app";


class Endpoints {
  @staticURL.endpoint({ state: "" })
  public doSomething({ state }) {
    // The type of the 'state' would be automatically deduced here!
  }
}

// Notice that we must create the `staticURL` _after_  class declaration.
// This still gives us only duck type -safety, but at least it is something
export const staticURL= app.endpointsFor(Endpoints, { url: "/hello" }); // We must use separate instance in order to make it even possible to infer types for methods, even after TS issue is solved! Can not use `app` itself directly to decorate different methods of different classes.
// main.ts
import app from "./app";
import myEndpoint from "./my-endpoint";

// Will be of type Array<tyras.AppEndpoint<TContext, TStateInfo>>.
export default app.materializeEndpoints(
  "/api/v1": [ myEndpoint ],
  ...
);

However, this will not work until this TS issue has been solved: microsoft/TypeScript#4881 . Perhaps its chances of survival are now marginally better, after TS 5 introduced non-experimental support for decorators.

As such, using decorators within TyRAS does not make sense at all until the TS issue is solved (?).

Specifying types of arguments explicitly is something I am not sure will be much difference from the current way of using builders and such. However, it is maybe worth exploring after 1.0.0 is out.

@stazz stazz self-assigned this May 24, 2023
@stazz
Copy link
Member Author

stazz commented May 24, 2023

On TyRAS side it would look something like this:

import * as builders from ".";
import type * as dataBE from "@ty-ras/data-backend";
import type * as data from "@ty-ras/data";

const createAppBuilder = <TStringDecoder>(): AppBuilder<TStringDecoder> => {
  const builder0 = builders.startBuildingAPI();
  const endpointsFor: AppEndpointSpecifierFactory<TStringDecoder> = (<
    TURLData extends dataBE.RuntimeAnyURLData,
  >({
    url,
  }:
    | AppEndpointSpecifierFactoryArgsStaticURL
    | AppEndpointSpecifierFactoryArgsURLParameters<
        TStringDecoder,
        TURLData
      >) => {
    const specifier:
      | AppEndpointSpecifier
      | AppEndpointSpecifierWithURL<TURLData> = {
      endpoint() {
        return (method: ClassMethodForEndpoint<any, any, any>, context) => {};
      },
    };
    return specifier;
  }) as any;
  return {
    endpointsFor,
  };
};

interface AppBuilder<TStringDecoder> {
  endpointsFor: AppEndpointSpecifierFactory<TStringDecoder>;
}

interface AppEndpointSpecifierFactory<TStringDecoder> {
  (args: AppEndpointSpecifierFactoryArgsStaticURL): AppEndpointSpecifier;
  <TURLData extends dataBE.RuntimeAnyURLData>(
    args: AppEndpointSpecifierFactoryArgsURLParameters<
      TStringDecoder,
      TURLData
    >,
  ): AppEndpointSpecifierWithURL<builders.EndpointHandlerArgsWithURL<TURLData>>;
}

interface AppEndpointSpecifierFactoryArgsStaticURL {
  url: string;
  // metadata: ...
}

interface AppEndpointSpecifierFactoryArgsURLParameters<
  TStringDecoder,
  TURLData extends dataBE.RuntimeAnyURLData,
> {
  url: {
    args: ReadonlyArray<string>;
    spec: dataBE.URLParameterValidatorSpec<TURLData, TStringDecoder>;
  };
  // metadata: ...
}

interface AppEndpointSpecifier {
  endpoint(args: {
    state: string;
  }): <This, Return>(
    method: ClassMethodForEndpoint<{}, This, Return>,
    context: ClassMethodDecoratorContext,
  ) => void;
}

// eslint-disable-next-line @typescript-eslint/no-empty-interface
interface AppEndpointSpecifierWithURL<TURLData extends object> {
  endpoint(args: {
    state: string;
  }): <This, Return>(
    method: ClassMethodForEndpoint<{ url: TURLData }, This, Return>,
    context: ClassMethodDecoratorContext,
  ) => void;
}

type ClassMethodForEndpoint<TArgs extends Record<string, any>, This, Return> = (
  this: This,
  args: { state: string } & TArgs,
) => Return;

@stazz
Copy link
Member Author

stazz commented May 24, 2023

Some notes

function endpointsFor<TClass>(
    theClass: new () => TClass,
  ): ((
    args: {...},
  ) => <Args extends Array<any>, Return>(
    method: (this: TClass, ...args: Args) => Return,
    ctx: ClassMethodDecoratorContext<TClass, typeof method>,
  ) => void) =>
  (args) =>
  (method, ctx) => {
    // Use 'theClass' and "===" comparison against 'this' to find correct builder and modify it.
  };

@stazz
Copy link
Member Author

stazz commented Aug 2, 2023

This is now implemented with #93 .

@stazz stazz closed this as completed Aug 2, 2023
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