brillout / wildcard-api

Functions as API.
MIT License
369 stars 14 forks source link

Wilcard current status #67

Closed cloudcompute closed 2 years ago

cloudcompute commented 2 years ago

Hi

I have a few questions. Would you pl. reply?

a. Wildcard is not maintained any longer. Can we still use it in production?

b. This package is not published on npm. Any specific reason?

c. There is a wildcard equivalent, namely, trpc. Here is the link: https://github.com/trpc/trpc/ Could you pl. give your opinion what shall we we use? I am looking forward to use Fastify in the backend and React SSR on frontend.

Thanks

brillout commented 2 years ago

Use Telefunc.

cloudcompute commented 2 years ago

Is telefunc production ready? Could you pl. tell what makes it superior than others?

cloudcompute commented 2 years ago

trpc supports Superjson and zod. Don't know whether telefunc supports it. A zod-like feature-rich validation library is a must. I hope we'd be able to use zod with telefunc, kindly confirm.

I am reluctant to use trpc because we need to code several things like router, context, protected-middleware on the server which is a tedious job. It has an adapter for Express but for Fastify, it has not.

telefunc synatx seems simpler.

brillout commented 2 years ago

Is telefunc production ready?

If you find any bug I'll fix it under 24h. (See my track record on vite-plugin-ssr.)

Could you pl. tell what makes it superior than others?

It's drastically simpler.

It's well documented. (Although many parts of the docs are still work-in-progress.)

Fastify

Telefunc's server middleware is agnostic: you can use it in any server environment, including Fastify or even Cloudflare Workers.

A zod-like feature-rich validation library is a must

Let me contradict you.

But before that, let me assure you that if you have a use case where it makes sense to use zod to define telefunction arguments, then I'll implement zod support.

For example something like this:

// CreateTodo.telefunc.js

const TodoItem = z.object({
  id: z.number(),
  text: z.string(),
  isCompleted: z.boolean()
})

shield(onNewTodo, TodoItem.pick({ text: true }))
async function onNewTodo({ text }) {
  // ...
}

But this is not Telefunc idiomatic. Let me explain.

A fundamental pilliar of Telefunc is inversion of control, see Telefunc > Tour > Inversion of control.

Let's imagine we implement a generic telefunction like the following:

const TodoItem = z.object({
  id: z.number(),
  text: z.string(),
  isCompleted: z.boolean(),
})

shield(updateTodoItem, [TodoItem])
export async function updateTodoItem({ id, text, isCompleted }) {
  const todoItem = await Todo.findOne({ id })
  todoItem.text = text
  todoItem.isCompleted = isCompleted
  await todoItem.save()
}

Here, it makes total sense to use Zod to define the telefunction arguments.

But, as explained in the link above, such a generic telefunction is considered bad practice in Telefunc's world.

Instead, we implement tailored telefunctions:

// TodoItem.telefunc.js

import { shield } from 'telefunc'
const t = shield.type

shield(onTextChange, [t.number, t.string])
export async function onTextChange(id, text) {
  const todoItem = await Todo.findOne({ id })
  todoItem.text = text
  await todoItem.save()
}

shield(onCompleteToggle, [t.number])
export async function onCompleteToggle(id) {
  const todoItem = await Todo.findOne({ id })
  todoItem.isCompleted = !todoItem.isCompleted
  await todoItem.save()
}

Let's make an analogy with a React component <CreateTodo />:

// CreateTodo.tsx

import { onNewTodo } from './CreateTodo.telefunc'

type ClickEvent = {
  target: {
    // Which one would you do?
    value: string
    // Or:
    value: z.infer<typeof TodoItem.pick({ text: true })>
  }
}

async function onClick(ev: ClickEvent) {
  await onNewTodo(ev.target.value)
}

function CreateTodo() {
  return (
    <form>
      <input input="text"></input>
      <button onClick={onClick}>Add To-Do</button>
    </form>
  )
}

You would pick value: string, right?

It's the same with telefunctions.

Think in terms of frontend events, instead of thinking in terms of backend types.

The arguments of frontend events are most often simple. Actually, I'd even say almost always. There are some use case where a frontend event can have complex arguments (e.g. web apps such as an online image editor) but these are rare.

For the same reason frontend events have simple arguments, telefunction also have simple arguments.

If you end up needing complex types for your telefunction arguments, you're most likely doing it wrong and trying to implement generic telefunctions.

My suggestion: follow the inversion of control approach and, if you still feel like you need to use your zod types for telefunction arugments, then hit me up on Discord or GitHub and I'll implement zod support.

cloudcompute commented 2 years ago

Thank you so much for offering the support like a professional within 24 hours for an open source project.

a. I do not want to use Zod to define the telefunction arguments. I am simply looking for validations like date, number: min-max, sting matching, regular expression matching for strong passwords, a value must be one among the following types.. enum

i. Not only zod, the developer should be able to use any validation library like yup, joi, superstruct etc. ii. Shared validation is an ideal solution. For example, create a file named validations.ts inside the shared folder. At client side, invoke these validations before we update a db. At client side, access this same set of validations before we submit a form to the server.

Here is a detailed list of different types of validations.. see its Readme.md file.. https://github.com/jquense/yup

b. SuperJSON allows us to able to transparently use e.g. standard Date/Map/Sets over the wire between the server and client. Can we use something like SuperJSON in telefunc?

Some suggestions: a. The following methods like register(), login() don't need to be prtoected while all other, let's say, 100 API routes need to be. Placing the Abort code inside the 100 methods is tedious.

Instead, use decorators just like NestJS does

@IsAuthenticated()
@IsRole("Admin")
async listUSers (...)

b. The usage of shield is monotonous. 100 RPC methods resulting in 100 shields.

shield(onCommentDelete, [shield.type.number])
export async function onCommentDelete(id) { ... }

Here the function name is duplicated. If 5 arguments, shield.type.xxx will occur 5 times. Also, a parameter type is missing in the function's signatures, readability suffers.

Probably, intead of shield, just use a decorator named RPC indicating that it can be accessed from the client side.

@RPC
const onCommentDelete = (id: number): boolean => { ... }

The telefunc should be able to infer the parameter and return types from within the method definition itself. No need of shield in this case.

Last but not the least, I really don't know whether all this stuff like Context, Authentication, Permission, Abort is really required. According to me, let the developer handle all this by himself. If telefunc still wants to provide support for Context, Abort, etc. good enough. But it should be optional, let him use whatever libraries and framework features he wants to use for Error Handling, Validation, making the Context available to RPC methods, etc.

To infer the types, this package might be of help to you: https://www.npmjs.com/package/reflect-metadata

Looking forward to hear from you.

Thanks

brillout commented 2 years ago

There is no reliable way to infer TS types at runtime, and reflect-metadata won't change that.

For validation, see https://telefunc.com/form-validation.

// Form.telefunc.ts

import { shield } from 'telefunc'
const t = shield.type

// Note the alternative syntax, which you'll probably like more
export const onFormSubmit = shield([t.string], async email => {
  if (!isEmailValid(email)) {
    return {
      invalidInputs: {
        email: 'Invalid email; make sure to enter a valid email.'
      }
    }
  }

  // ...
}

// Or any validation library like yup, joi, superstruct etc.
function isEmailValid() {
  return email.includes('@')
}

Telefunc uses @brillout/json-s which is a superior alternative to SuperJSON.

As for your other questions, have a closer look at the documentation, you'll see that most of your concerns are already addressed.

cloudcompute commented 2 years ago

This syntax is better than the previous one. I'll read the documentation.

Thanks

brillout commented 2 years ago

Feel free to further ask questions after you're more familiar with Telefunc's way of doing things.

brillout commented 2 years ago

There is no reliable way to infer TS types at runtime, and reflect-metadata won't change that.

Actually, we could make it work.

I'm working on other things right now, but if you'd be up to contribute we can start implementing it.

cloudcompute commented 2 years ago

I could not get back to you earlier. For now, I decided not to use the RPC in my current project. I am sorry I won't be able to devote time as I quite busy with the new project.

This link may give you some leads if you want to implement it.