Playground
This is the real liaise build, running in your browser. Pick an example, change it and run it again: the Network and Console panels show what happened. The examples call a fake API at api.example.com, so every failure is repeatable. Point baseUrl or endpoint anywhere else and the request goes out for real (the server has to allow cross-origin requests).
import { createApi, defineRequest } from 'liaise'
type User = { id: string; name: string; email: string }
// The path decides which params are required.
const getUser = defineRequest<User>()({ method: 'GET', path: '/users/:id' })
const api = createApi({
baseUrl: 'https://api.example.com',
requests: { getUser },
})
// This never throws: you always get { data, error }.
const { data, error } = await api.getUser({ id: '42' })
if (error) {
console.log(error.kind, error.status)
} else {
console.log(`Hello, ${data.name}`) // data is a User here
}import { createApi, defineRequest } from 'liaise'
type User = { id: string; name: string }
const getUser = defineRequest<User>()({
method: 'GET',
path: '/users/:id',
timeout: 1000,
})
const api = createApi({ baseUrl: 'https://api.example.com', requests: { getUser } })
// Each id makes the fake server fail in a different way.
for (const id of ['42', '404', '500', 'offline', 'slow']) {
const { data, error } = await api.getUser({ id })
if (error) console.log(id, '→', error.kind, error.status)
else console.log(id, '→', data.name)
}import { createApi, defineRequest } from 'liaise'
const search = defineRequest<string[], { q: string }>()({
method: 'GET',
path: '/search',
dedupe: true, // a new call cancels the one still in flight
})
const api = createApi({ baseUrl: 'https://api.example.com', requests: { search } })
// Someone types "liaise", one key every 60 ms.
// The server answers short queries more slowly.
const keystrokes = ['l', 'li', 'lia', 'liai', 'liais', 'liaise']
await Promise.all(
keystrokes.map(async (q, i) => {
await new Promise((wait) => setTimeout(wait, i * 60))
const { data, error } = await api.search({ q })
if (error?.kind === 'abort') console.log(`"${q}" cancelled`)
else if (data) console.log(`"${q}" shows`, data)
}),
)import { createApi, defineRequest } from 'liaise'
type User = { id: string; name: string }
const getUser = defineRequest<User>()({
method: 'GET',
path: '/users/:id',
share: true, // identical calls in flight share one request
})
const api = createApi({ baseUrl: 'https://api.example.com', requests: { getUser } })
// Five components ask for the same user at the same moment.
const results = await Promise.all(
[1, 2, 3, 4, 5].map(() => api.getUser({ id: '42' })),
)
const answered = results.filter((r) => r.data).length
console.log(`${answered} of ${results.length} callers got the user`)import { createApi, defineRequest } from 'liaise'
import { retryMiddleware } from 'liaise/middleware'
type User = { id: string; name: string }
const getUser = defineRequest<User>()({
method: 'GET',
path: '/users/:id',
timeout: 3000, // one deadline for the whole call, retries included
})
const api = createApi({
baseUrl: 'https://api.example.com',
requests: { getUser },
middleware: [retryMiddleware(3)], // retries 5xx responses
})
// 'flaky' fails twice, then answers.
const { data, error } = await api.getUser({ id: 'flaky' })
if (error) console.log('gave up:', error.kind, error.status)
else console.log('got', data.name, 'on the third try')import { createGraphQL, Operation, gql } from 'liaise'
type User = { name: string; posts: { title: string }[] }
// The second type is data, keyed by the field the operation selects.
const getUser = new Operation<{ id: string }, { user: User }>({
operation: gql`query GetUser($id: ID!) { user(id: $id) { name posts { title } } }`,
})
const renameUser = new Operation<{ id: string; name: string }, { renameUser: { name: string } }>({
operation: gql`mutation RenameUser($id: ID!, $name: String!) { renameUser(id: $id, name: $name) { name } }`,
})
const api = createGraphQL({
endpoint: 'https://api.example.com/graphql',
queries: { getUser },
mutations: { renameUser },
timeout: 1000,
})
// The same Result as REST: a GraphQL error is an error, never a throw.
const id = '42'
const { data, error } = await api.query.getUser({ id })
if (error) {
console.log(error.kind, error.status)
if (error.kind === 'http') console.log('body:', error.body)
if (error.partialData) console.log('partial data:', error.partialData)
} else {
console.log(data.user.name, 'wrote', data.user.posts.map((p) => p.title))
}
const renamed = await api.mutation.renameUser({ id, name: 'Ada King' })
if (renamed.error) console.log('rename:', renamed.error.kind, renamed.error.status)
else console.log('renamed to', renamed.data.renameUser.name)import { createApi, defineRequest, pollUntil } from 'liaise'
type Job = { id: string; status: 'queued' | 'running' | 'done'; result?: string }
const getJob = defineRequest<Job>()({ method: 'GET', path: '/jobs/:id' })
const api = createApi({ baseUrl: 'https://api.example.com', requests: { getJob } })
// Ask every 300 ms until the job is done, and give up after 5 seconds.
const { data, error } = await pollUntil(api.getJob, { id: '42' }, {
every: 300,
until: r => r.data.status === 'done',
giveUpAfter: 5000,
})
if (error) console.log(`gave up: ${error.kind} ${error.status}`)
else console.log(`job ${data.id} is ${data.status}: ${data.result}`)Network
Requests to other sites fail like this when you’re offline, the URL is wrong, or the site doesn’t allow cross-origin requests (CORS).
Console
Change the id to '404', '500' or 'offline' and run it again. Hover over data or error to see the types.
Ctrl+Space or ⌘I for suggestions · ⌘↵ to run or stopEditing works on a larger screen.