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

v3 notes #87

Open
stazz opened this issue May 3, 2024 · 0 comments
Open

v3 notes #87

stazz opened this issue May 3, 2024 · 0 comments
Assignees

Comments

@stazz
Copy link
Member

stazz commented May 3, 2024

Ideas (big pic inspired by Hono RPC):

Package @ty-ras/rest-api (new)

Something like this

export type APIInfoGeneric<TValidator> = Record<string, SingleAPIInfoGeneric<TValidator>>;
export type SingleAPIInfoGeneric<TValidator> = SingleAPIInfoNamedGeneric<TValidator> &
  SingleAPIInfoWithMethodsGeneric<TValidator>;
export interface SingleAPIInfoNamedGeneric<TValidator> {
  [key: string]: SingleAPIInfoGeneric<TValidator>;
}
export type SingleAPIInfoWithMethodsGeneric<TValidator> = Partial<
  Record<HTTPMethodSymbols, EndpointInfoGeneric<TValidator>>
>;
export type HTTPMethodSymbols = typeof GET | typeof POST;
export type EndpointInfoGeneric<TValidator> = EndpointInfoResponseBodyGeneric<TValidator> &
  EndpointInfoQueryGeneric<TValidator> &
  EndpointInfoRequestHeadersDataGeneric<TValidator> &
  EndpointInfoResponseHeadersDataGeneric<TValidator>;
export interface EndpointInfoResponseBodyGeneric<TValidator> {
  responseBody: TValidator;
}
export interface EndpointInfoURLGeneric<TValidator> {
  url?: Record<string, TValidator>;
}
export interface EndpointInfoQueryGeneric<TValidator> {
  query?: Record<string, TValidator>;
}
export interface EndpointInfoRequestHeadersDataGeneric<TValidator> {
  requestHeaders?: Record<string, TValidator>;
}
export interface EndpointInfoResponseHeadersDataGeneric<TValidator> {
  responseHeaders?: Record<string, TValidator>;
}
export const GET = Symbol('GET');
export const POST = Symbol('POST');

And then

export const testing = {
  test: {
    another: {
      [GET]: {
        responseBody: 'validator',
      },
    },
    [POST]: {
      responseBody: 'validator',
    },
  },
} as const satisfies APIInfoGeneric<string>;

Package @ty-ras/rest-api-io-ts (new)

Has

import type * as endpoints from '@ty-ras/rest-api';
import type * as t from 'io-ts';

export type AnyValidator = t.Any;
export type APIInfo = endpoints.APIInfoGeneric<AnyValidator>;
export type SingleAPIInfoGeneric = endpoints.SingleAPIInfoGeneric<AnyValidator>;
export type SingleAPIInfoNamed = endpoints.SingleAPIInfoNamedGeneric<AnyValidator>;
export type SingleAPIInfoWithMethods = endpoints.SingleAPIInfoWithMethodsGeneric<AnyValidator>;
export type EndpointInfo = endpoints.EndpointInfoGeneric<AnyValidator>;
export type EndpointInfoResponseBody = endpoints.EndpointInfoResponseBodyGeneric<AnyValidator>;
export type EndpointInfoURL = endpoints.EndpointInfoURLGeneric<AnyValidator>;
export type EndpointInfoQuery = endpoints.EndpointInfoQueryGeneric<AnyValidator>;
export type EndpointInfoRequestHeadersData =
  endpoints.EndpointInfoRequestHeadersDataGeneric<AnyValidator>;
export type EndpointInfoResponseHeadersData =
  endpoints.EndpointInfoResponseHeadersDataGeneric<AnyValidator>;

Consequentially, then

export const testing = {
  test: {
    another: {
      [endpoints.GET]: {
        responseBody: t.string,
      },
    },
    [endpoints.POST]: {
      responseBody: t.boolean,
    },
  },
} as const satisfies APIInfo;

Package @ty-ras/endpoint-spec (pre-existing)

The ApplicationBuilderGeneric would need to have something like

  createEndpoints: <TEndpoints extends [EndpointCreationArg, ...Array<EndpointCreationArg>]>(
    this: void,
    mdArgs: {
      [P in keyof TMetadataProviders]: md.MaterializeParameterWhenCreatingEndpoints<
        TMetadataProviders[P]
      >;
    },
    ...endpoints: TEndpoints
  ) => EndpointsCreationResult<
    TMetadataProviders,
    TServerContextArg,
    dataBE.MaterializeStateInfo<
      TStateHKT,
      dataBE.MaterializeStateSpecBase<TStateHKT>
    >,
    InferAPIInfoType<TEndpoints> // <-- new generic parameter!
  >;

Backend builds application, which is runnable + exposes object which is as const satisfies APIInfo.
This object is then used by FE to build full client.
Args for when FE building client:

  • URL prefix
  • Callback to get (auth) headers

Notice that this particular scenario we would end up importing Node things into FE package.
Because we want validation (= transformation) to also happen on FE end (e.g. to handle Date <-> string automatic translation by validation framework like io-ts and more recently, zod), we must get the validation objects to FE also (unlike Hono RPC which gets only typings, and has no runtime validation on FE side).
We might end up with something like this

// spec.ts, shared between BE and FE
import type * as tyras from "@ty-ras/rest-api-io-ts";
import * as t from "io-ts";

export default {
  test: {
    another: {
      [tyras.GET]: {
        responseBody: t.string,
      },
    },
    [tyras.POST]: {
      responseBody: t.boolean,
    },
  },
}  as const satisfies tyras.APIInfo;
// backend.ts, BE-only
import * as tyras from "@ty-ras/backend-node-io-ts-openapi";
import spec from "./spec";
 
export const app = tyras.buildApp(
  spec,
  {
   test: {
     another: {
       [tyras.GET]: (args) => implementation,
     },
     [tyras.POST]: (args) => implementation,
   },
  }
);
// frontend.ts, FE-only
import * as tyras from "@ty-ras/frontend-fetch-io-ts";
import spec from "@app/backend/spec";

export const api = tyras.buildClient(
   spec,
   [ urlPrefix = "/prefix" ],
);

// Usage:
const stringResult = await api.test.another[tyras.GET](args);

This makes #72 obsolete

@stazz stazz self-assigned this May 3, 2024
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