shield()
Environment: server.
You use shield() to guarantee the type of telefunction arguments. (As explained in Learn > 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 you use TypeScript, then Telefunc automatically defines
shield(), see TypeScript - Automatic.
TypeScript - Automatic
If you use TypeScript, then Telefunc automatically generates shield() for each telefunction.
In other words: telefunction argument types are automatically validated at runtime:
// hello.telefunc.ts
// No need to define a shield() when using TypeScript: Telefunc automatically generates
// it. 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 you seamlessly re-use types across your frontend and backend code, but you also get automatic type-safety at runtime. If you use a TypeScript ORM (e.g. Prisma) or SQL builder (e.g. Kysely and others), then you get end-to-end type safety all the way from database to frontend.
For a faster development, Telefunc doesn't generate
shield()and your telefunction arguments aren't validated during development. Telefunc only generatesshield()upon building your app for production. You can enable the generation ofshield()for development by settingconfig.shield.devtotrue.
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 and Parcel), you need to define
shield()manually: see TypeScript - Manual.
TypeScript - Manual
If you define shield() manually (instead of using Telefunc's automatic shield() generator as described in TypeScript - Automatic), then note that you 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: server
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.type | TypeScript | JavaScript |
|---|---|---|
t.string | string | typeof value === 'string' |
t.number | number | typeof value === 'number' |
t.boolean | boolean | value === true || value === false |
t.date | Date | value.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 const | value === val |
t.optional(T) | T | undefined | isT(value) || value === undefined |
t.nullable(T) | T | null | isT(value) || value === null |
t.any | any | true |