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.
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, vite-plugin-ssr, 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.
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`
}
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'
}
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 |