Abort() & shield()

Reading Recommendation: Overview > Tour.

As we have seen in the Telefunc tour, telefunctions are public and need protection. We use throw Abort() and shield() to protect our telefunctions.

throw Abort()

By using throw Abort() we essentially cancel forbidden telefunction calls:

// TodoList.telefunc.js
// Environment: Node.js

// `getTodoList()` is a public: it can be called not only by our frontend but really by anyone
export { getTodoList }

import { getContext } from 'telefunc'

async function getTodoList() {
  const { user } = getContext()

  // We forbid `getTodoList()` to be called by a user that is not not logged-in
  if (!user) {
    throw Abort()
  }

  const todoList = await Todo.findMany({ authorId: user.id })
  return todoList
}

Here, in essence, we use throw Abort() to implement a permission: only a logged-in user can fetch its to-do items. We talk more about permissions at Guides > Permissions.

In principle, we could also throw new Error() instead of throw Abort() as it also achieves the job of canceling the telefunction, but Abort() comes with many conveniences and we therefore recommend using throw Abort().

If, upon canceling a telefunction call, we want to pass information to the frontend then we use return someValue or throw Abort(someValue), which we talk more about at Guides > Permissions.

shield()

We can also use throw Abort() to ensure the type of telefunction arguments:

// CreateTodo.telefunc.js
// Environment: Node.js

export async function onNewTodo(args) {
  if (
    args?.constructor !== Object ||
    typeof args.text !== 'string' ||
    typeof args.isCompleted !== 'boolean'
  ) {
    throw Abort()
  }
  const { text, isCompleted } = args
  /* ... */
}

But, for more convenience, we can use shield() instead:

// CreateTodo.telefunc.js
// Environment: Node.js

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

shield(onNewTodo, [{ text: t.string, isCompleted: t.boolean }])
export async function onNewTodo({ text, isCompleted }) {
  // ...
}

Not only does shield() call throw Abort() on our behalf, but it also infers the arguments' type for IntelliSense and TypeScript.

See also:

Random telefunction calls

Because telefunctions are public, any of our telefunction can be called by anyone at any time with any arguments.

One way to think about it is that any random telefunction call can happen at any time.

This means we should always protect our telefunctions, even if know our frontend to call a telefunction only in a certain way. For example:

// Comment.jsx
// Environment: Browser

import { onCommentDelete } from './Comment.telefunc.js'

function Comment({ id, text, context }) {
  const deleteButton =
    // Note how we only show the delete button to admins
    context.user.isAdmin ?
      <button onClick={() => onCommentDelete(id)}>Delete</button> :
      null
  return <>
    <p>{ text }</p>
    { deleteButton }
  </>
}

Because our frontend shows the delete button only to admins, we can assume the user to be an admin whenever our frontend calls onCommentDelete().

But we still use throw Abort() to protect our telefunction against calls not originating from our frontend.

// Comment.telefunc.js
// Environment: Node.js

import { getContext, Abort, shield } from 'telefunc'

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

  // `onCommentDelete()` is public and anyone can call it while not being an admin.
  // If that happens, we make to sure to cancel the telefunction call.
  if (!user.isAdmin) {
    throw Abort()
  }

  // ...
}