shield()

Environment: Server.

We use shield() to assert telefunction arguments; as explained in RPC, telefunctions are public and need protection.

TypeScript

Telefunc can automatically generate shield() for telefunctions.

Telefunc automatically generates shield() only for stacks that transpile server-side code (Next.js, Vite, vite-plugin-ssr, SvelteKit, Nuxt, etc.).

// hello.telefunc.ts

// We don't need to define a shield() when using TypeScript: Telefunc automatically generates
// it for us. For example here, Telefunc automatically aborts the telefunction call if the
// argument is `hello({ name: 42 })` and throws an error that `name` should be a `number`.
export async function hello({ name }: { name: string }) {
   return `Welcome to Telefunc, ${name}.`
}

This means that, not only can we seamlessly use types across frontend-backend, but we also get automatic runtime type-safety.

When using Telefunc with a TypeScript ORM (e.g. Prisma) or SQL builder (e.g. Kysely or others), we get end-to-end type safety all the way from database to frontend.

For stacks that don't transpile server-side code (e.g. React Native, CRA, Parcel), we need to define shield() ourselves.

TypeScript automatically infers the type of telefunction arguments using shield():

import { shield } from 'telefunc'

export const onNewTodo = shield(
  [shield.type.string],
  async function (text) {
    // ✅ TypeScript knows that `text` is of type `string`
  }
)

Note that the following won't work:

import { shield } from 'telefunc'

shield(onNewTodo, [shield.type.string])
// TypeScript cannot infer the type of named functions by design.
export async function onNewTodo(text) {
  // ❌ TypeScript doesn't know that `text` is of type `string`
}

Common types

Examples showcasing the most common shield() types:

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

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

shield(onTextChange, [t.number, t.string])
async function onTextChange(id, text) {
  // typeof id === 'number'
  // typeof text === 'string'
}

shield(onCompletedToggle, [{ id: t.number, isCompleted: t.boolean }])
async function onCompletedToggle({ id, isCompleted }) {
  // typeof id === 'number'
  // typeof isCompleted === 'boolean'
}

shield(onTagListChange, [t.array(t.string)])
async function onTagListChange(tagList) {
  // tagList.every(tagName => typeof tagName === 'string')
}

shield(onNewMilestone, [{
  name: t.string,
  deadline: t.nullable(t.date),
  ownerId: t.optional(t.number)
}])
async function onNewMilestone({ name, deadline, ownerId }) {
  // typeof name === 'string'
  // deadline === null || deadline.constructor === Date
  // ownerId === undefined || typeof ownerId === 'number'
}

shield(onStatusChange, [t.or(
  t.const('DONE'),
  t.const('PROGRESS'),
  t.const('POSTPONED')
)])
async function onStatusChange(status) {
  // status === 'DONE' || status === 'PROGRESS' || status === 'POSTPONED'
}

All types

List of shield() types:

const t = shield.typeTypeScriptJavaScript
t.stringstringtypeof value === 'string'
t.numbernumbertypeof value === 'number'
t.booleanbooleanvalue === true || value === false
t.dateDatevalue.constructor === Date
t.array(T)T[]value.every(element => isT(element))
t.object(T)Record<string, T>Object.values(value).every(v => isT(v))
{ k1: T1, k2: T2, ... }{ k1: T1, k2: T2, ... }isT1(value.k1) && isT2(value.k2) && ...
t.or(T1, T2, ...)T1 | T2 | ...isT1(value) || isT2(value) || ...
t.tuple(T1, T2, ...)[T1, T2, ...]isT1(value[0]) && isT2(value[1]) && ...
t.const(val)val as constvalue === val
t.optional(T)T | undefinedisT(value) || value === undefined
t.nullable(T)T | nullisT(value) || value === null
t.anyanytrue