elysiajs / elysia

Ergonomic Framework for Humans
https://elysiajs.com
MIT License
9.09k stars 193 forks source link

Rxjs pipe syntax #668

Open mdbetancourt opened 1 month ago

mdbetancourt commented 1 month ago

What is the problem this feature would solve?

Before rxjs used to uses chaining syntax (as elysia) but they changed to pipe syntax the reason they given was

Any library importing the patch operator augmenting the observable prototype would also augment it for all consumers. For example, when a library imports map extension from import 'rxjs/add/operators/map', it makes map available for all code importing the library itself. This could bring confusion due to the inconsistent way of importing extensions. Some places would not need to important map (those were the library is used) while in other place of the code we would have to else the compiler would complain. The other issue would be that if we did not import the extension and relied (intentionally or not) on the import of the library we consume, the day they remove the import, our application will break. This point is more on the optimization side. Unused operators added on prototype can’t be removed during tree-shaking tools like webpack. Operators patches imported but not used can’t be deleted by linters. https://kimsereyblog.blogspot.com/2018/11/moving-from-chaining-to-piping-in-rxjs.html

i thing pipe syntax has some good advantage

What is the feature you are proposing to solve the problem?

I wrote a semi-working example of how would looks

function plugin<T extends Context, A>(
    fn1: UnaryFunction<T, A>,
): UnaryFunction<T, A>
function plugin<T extends Context, A, B>(
    fn1: UnaryFunction<T, A>,
    fn2: UnaryFunction<A, B>,
): UnaryFunction<T, B>
function plugin<T extends Context, A, B, C>(
    fn1: UnaryFunction<T, A>,
    fn2: UnaryFunction<A, B>,
    fn3: UnaryFunction<B, C>,
): UnaryFunction<T, C>
function group<T extends Context, A>(
    fn1: UnaryFunction<T, A>,
): UnaryFunction<T, A>
function group<T extends Context, A, B>(
    fn1: UnaryFunction<T, A>,
    fn2: UnaryFunction<A, B>,
): UnaryFunction<T, B>
function group<T extends Context, A, B, C>(
    fn1: UnaryFunction<T, A>,
    fn2: UnaryFunction<A, B>,
    fn3: UnaryFunction<B, C>,
): UnaryFunction<T, C>

type Context = {
    definitions: {
        type: {}
        error: {}
    }
    singleton: {
        decorator: {}
        derive: {}
        resolve: {}
        store: {}
    }
    metadata: {
        schema: {}
        macro: {}
    }
}

type Derive<T extends Context, Value> = T & { singleton: { derive: Value } }

type ContextObject<T extends Context> = T['singleton']['derive']

function derive<const Ctx extends Context, const Result>(
    fn: (ctx: ContextObject<Ctx>) => Result,
): UnaryFunction<Ctx, Derive<Ctx, Result>>

function post<
    const Ctx extends Context,
    const Path extends string,
    const Handle extends (app: ContextObject<Ctx>) => unknown,
>(path: Path, handler: Handle): UnaryFunction<Ctx, Ctx>

type UnaryFunction<T, R> = (source: T) => R

/**
   * USAGE
   */ 
function main() {
  const auth = plugin(
    derive(() => ({ signin: () => console.log('signin') }))
  )
  const t = plugin(
     derive(() => ({ logger: (msg: string) => console.log(msg) })),
   )
  plugin(
    auth,
    t,
    post('/', ({ signin, logger }) => {
      signin()
      logger('test')
    })
  )

  plugin(
//  scope('global'),
// guard(...) // global schema
    derive(() => ({ signin: () => console.log('signin') })),
    derive(() => ({ logger: (msg: string) => console.log(msg) })),
// schema({ ... }), // this schema only apply to next handler (post) not get handler
    post('/', ({ signin, logger }) => {
      signin()
      logger('test')
    }),
// get(.....)
  )

  plugin(
    derive(() => ({ signin: () => console.log('signin') })),
    derive(() => ({ logger: (msg: string) => console.log(msg) })),
    group(
//   prefix('/user'),
      post('/', ({ signin, logger }) => {
        signin()
        logger('test')
      })
    )
  )

  plugin(
    plugin(derive(() => ({ signin: () => console.log('signin') }))),
    plugin(derive(() => ({ logger: (msg: string) => console.log(msg) }))),
    post('/', ({ signin, logger }) => {
      signin()
      logger('test')
    })
  )
}

i think it would help to make the project easier to maintain, extend etc

What alternatives have you considered?

No response