shield()

Environment: Server.

We use shield() to guarantee the type of telefunction arguments. (As explained in RPC, telefunctions are public and need protection.)

// CreateTodo.telefunc.js
// Environment: Server

export { onNewTodo }

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

shield(onNewTodo, [t.string])
async function onNewTodo(text) {
  // `text` is guaranteed to be a `string`: if `onNewTodo(42)` is called then Telefunc
  // throws an error that `text` should be a `string` (instead of a `number`)
}

If we use TypeScript, then Telefunc automaticaly defines shield(), see TypeScript - Automatic.

TypeScript - Automatic

If we use TypeScript, then Telefunc automatically generates shield() for each telefunction.

In other words: telefunction argument types are automatically validated at runtime:

// 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}.`
}

With Telefunc, not only can we seamlessly re-use types across our frontend and backend code, but we also get automatic type-safety at runtime. If we use a TypeScript ORM (e.g. Prisma) or SQL builder (e.g. Kysely and others), then we get end-to-end type safety all the way from database to frontend.

Telefunc's automatic shield() generation only works for stacks that transpile server-side code (Next.js, Vite, Vike, SvelteKit, Nuxt, etc.).

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

TypeScript - Manual

If we define shield() manually (instead of using Telefunc's automatic shield() generator as described in TypeScript - Automatic), then note that we don't need to define the arguments type twice:

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 doesn'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