После деплоя CLI печатает адрес вида https://my-api.tessera.app и список точек, куда уехал бандл. Дальше правьте src/index.js и повторяйте tessera deploy — предыдущие версии остаются доступными 90 дней.
Локальная разработкаtessera dev поднимает тот же рантайм на localhost:8787, включая KV и cron. Отличий от продакшена нет, кроме географии.
Структура проекта
дерево
my-api/
src/index.js # точка входа, экспортирует handler
tessera.json # конфигурация: маршруты, cron, биндинги
.env.preview # переменные для превью-окружения
.tessera/ # кеш сборки, в git не нужен
Установка CLI
CLI — единственный обязательный инструмент. Нужен Node.js 18 или новее; сам рантайм на машине не требуется.
основные команды
# разработка
$ tessera dev --port 8787 --kv-persist
# деплой
$ tessera deploy # в превью-окружение
$ tessera deploy --prod # в продакшен, во все точки
# наблюдение
$ tessera logs --tail --pop dme1
$ tessera trace --last 20 --min-ms 40
# секреты
$ tessera secret put STRIPE_KEY --env prod
$ tessera secret ls
# версии
$ tessera versions
$ tessera rollback v41
Изолят не хранит состояние
Глобальные переменные живут, пока живёт изолят, — от нескольких секунд до нескольких минут. Использовать их как кеш можно, как источник истины — нельзя.
Роутинг
Маршруты объявляются в tessera.json на уровне домена, а внутри функции путь разбирается как вам удобно. Готового роутера в рантайме нет — есть URLPattern.
src/index.js
const routes = [
{ m: "GET", p: new URLPattern({ pathname: "/users/:id" }), h: getUser },
{ m: "POST", p: new URLPattern({ pathname: "/users" }), h: createUser }
];
export async function handler(request, env, ctx) {
for (const r of routes) {
if (r.m !== request.method) continue;
const match = r.p.exec(request.url);
if (match) return r.h(request, env, match.pathname.groups);
}
return Response.json({ error: "not_found" }, { status: 404 });
}
Приоритет шаблонов
Если запрос подходит под несколько маршрутов из конфигурации, выигрывает более специфичный: сначала точное совпадение хоста и пути, затем шаблон с *, затем маршрут по умолчанию.
Шаблон
Запрос
Приоритет
api.example.ru/v2/orders
/v2/orders
1
api.example.ru/v2/*
/v2/orders
2
api.example.ru/*
/v2/orders
3
*.example.ru/*
/v2/orders
4
Запрос и ответ
request — стандартный Request плюс поле geo, которое заполняет точка присутствия.
Отвечать можно до того, как готов весь payload. Соединение разрешено держать до 15 минут — этого хватает и на SSE, и на прокси к языковой модели.
SSE
export async function handler(request, env, ctx) {
const { readable, writable } = new TransformStream();
const writer = writable.getWriter();
const enc = new TextEncoder();
ctx.waitUntil((async () => {
for (let i = 1; i <= 5; i++) {
await writer.write(enc.encode(`data: тик ${i}\n\n`));
await new Promise(r => setTimeout(r, 1000));
}
await writer.close();
})());
return new Response(readable, {
headers: {
"content-type": "text/event-stream",
"cache-control": "no-cache",
"x-accel-buffering": "no"
}
});
}
Что считается временем ответа
Лимит 15 минут — на всё соединение. Лимит 50 мс — только на процессорное время. Ожидание сети и KV в CPU не входит.
Окружение и секреты
Второй аргумент env собирается из трёх источников: переменные из tessera.json, секреты из хранилища и биндинги ресурсов (KV, Blob, Counters).
терминал
$ tessera secret put STRIPE_KEY --env prod
? Значение: ********************
✔ Записано. Значение больше не покажется.
$ tessera secret ls --env prod
STRIPE_KEY обновлён 2 дня назад
JWT_SECRET обновлён 14 дней назад
Секрет шифруется на вашей машине и расшифровывается только внутри изолята. В логах, трейсах и дампах значение заменяется на [secret] — включая случаи, когда вы напечатали его сами.
Что
Где задаётся
Видно в логах
Переменная
tessera.json → vars
да
Секрет
tessera secret put
нет, маскируется
Биндинг
tessera.json → kv / blob
только имя
REGION
рантайм
да
KV-хранилище
KV — ключ-значение с репликацией на все точки. Чтение из локальной реплики занимает около 0,4 мс, запись сходится по сети за две секунды. Это модель «согласованность в конечном счёте»: сразу после записи другая точка может отдать старое значение.
основные операции
// чтение
const raw = await env.CACHE.get("user:42"); // строка или null
const obj = await env.CACHE.get("user:42", "json"); // сразу разобранный JSON
// запись с временем жизни
await env.CACHE.put("user:42", JSON.stringify(user), { ttl: 3600 });
// перебор по префиксу
const { keys, cursor } = await env.CACHE.list({ prefix: "user:", limit: 100 });
// удаление
await env.CACHE.delete("user:42");
Когда KV не подходит
Счётчики и лимиты — берите COUNTERS: они атомарны в пределах точки.
Данные, где важна строгая консистентность, — держите во внешней базе и обращайтесь к ней явно.
Больше одной записи в секунду на один ключ — сверх лимита записи начнут отбрасываться.
Ключ — это часть API
Переименовать ключ без миграции нельзя: старые изоляты продолжают жить до нескольких минут и будут читать прежнее имя. Пишите оба ключа один релиз, потом убирайте старый.
Cron и очереди
Расписание описывается в конфигурации, обработчик экспортируется рядом с основным. Задача выполняется ровно один раз: точка-лидер выбирается автоматически, остальные пропускают запуск.
src/index.js
export async function scheduled(event, env, ctx) {
// event.cron — выражение, event.scheduledTime — плановое время
const stale = await env.CACHE.list({ prefix: "session:" });
for (const key of stale.keys) {
if (key.expiresAt < Date.now()) await env.CACHE.delete(key.name);
}
console.log("подчищено ключей:", stale.keys.length);
}
Отладить расписание можно локально, не дожидаясь нужной минуты:
Метрики собираются по точкам присутствия: запросы, ошибки, p50/p95/p99 задержки, процессорное время, попадания в KV. Живая сводка — на странице статуса.
Лимиты
Полная таблица — на странице платформы. Здесь то, обо что чаще всего спотыкаются.
Лимит
Значение
Что происходит при превышении
Процессорное время
50 мс
запрос обрывается, код 1102
Память изолята
128 МБ
изолят снимается, код 1103
Размер бандла
10 МБ
деплой не проходит
Подзапросов fetch
50
fetch бросает исключение
Записей KV на ключ
1/с
лишние записи отбрасываются молча
Как узнать, сколько CPU тратит функцияtessera trace --last 20 --min-ms 40 покажет самые тяжёлые запросы с разбивкой по фазам. В песочнице то же самое видно на вкладке «Тайминги».
Коды ошибок
Платформенные ошибки приходят с кодом 11xx в заголовке x-tessera-error и в логах. Ошибки вашего кода — это обычные исключения, они попадают в лог целиком со стеком.
Код
Что случилось
Что делать
1101
Исключение в handler
смотреть стек в tessera logs
1102
Превышено процессорное время
вынести тяжёлое в ctx.waitUntil или в WASM
1103
Превышена память
не собирать большие массивы в памяти, работать потоком