Перейти к содержимому

ONREZA Functions

ONREZA Functions — beta runtime для быстрых Bun handlers, которые не требуют постоянного server process. Используйте их для коротких атомарных flow: функция экспортирует fetch(request, ctx), получает явный ctx и возвращает стандартный Response.

ONREZA Functions не являются слоем build output manifest. nrz deploy публикует полный список function entry-файлов отдельным payload рядом с обычным SOURCE_BUNDLE_V1, а платформа создаёт immutable source snapshots, revisions и единый environment release.

Целевая модель:

  • функция — это live-ресурс уровня проекта, адресуемый стабильным триггером (route или расписание), а не build-артефакт с unique URL;
  • каждый nrz deploy передаёт полный Functions snapshot для целевого окружения: отсутствующая в новом snapshot функция перестаёт быть активной после успешного release;
  • каждый entry создаёт immutable source snapshot из одного self-contained файла и новую revision;
  • snapshot хранится как bounded DB source snapshot, а не как S3/archive bundle;
  • активация переключает active environment release: app artifact pointer, function-key → revision map и edge ruleset меняются как одно состояние;
  • production, preview и каждое из ваших окружений имеют независимый указатель: изменения функций на preview-ветке не трогают production;
  • rollback — это обратное переключение указателя на прошлый release.

Function identity и revision остаются отдельными от app artifact, но в beta function source публикуется только через nrz deploy. Новый Functions snapshot активируется атомарно с успешным app release; неуспешный deploy не меняет live functions. Standalone publish остаётся только для Edge Rules и не принимает function source.

Используйте ONREZA Functions для:

  • API handlers и webhook endpoints
  • route pipeline steps: auth, redirects, geo-routing, A/B, security headers вокруг Static/Compute или terminal-функции
  • лёгких proxy/adapter routes
  • request-time JSON transforms
  • маленьких backend-for-frontend endpoints

Не используйте ONREZA Functions для dependencies, static assets, generated bundles, sourcemaps, WebSocket, долгоживущего HTTP server, SSR-фреймворков (Next.js, Remix, Astro SSR), native modules или больших фоновых jobs. Для этого используйте ONREZA Compute или размещение статических сайтов.

Entry файл должен быть self-contained ESM module. Основной публичный путь beta — HTTP fetch. В начале файла объявите статический config: CLI/server извлекают его через AST на publish, не исполняя пользовательский код. Для HTTP route wiring используйте onreza.rules.toml, а не config.triggers.

CLI ищет функции в проекте по брендовому суффиксу *.nrz-fn.ts, *.nrz-fn.tsx, *.nrz-fn.js, *.nrz-fn.jsx или *.nrz-fn.mjs, пропуская vendor/build/cache директории. Имя функции по умолчанию выводится из имени файла до суффикса: src/api/hello.nrz-fn.ts становится hello. Если в config указан name, он имеет приоритет. Для группировки используйте явное имя, например api-hello, в config.name или в имени файла. Одинаковые effective names отклоняются publish-инвариантом платформы.

В v1 одна функция публикуется как один entry-файл. Relative imports в другие файлы проекта, shared helper files, assets и npm dependencies не входят в публичный contract: скопируйте helper-код в entry-файл или используйте Compute, если нужен полноценный dependency graph.

В open beta ONREZA Functions не публикуют local modules, node_modules или npm dependencies. Это не означает, что shared code не появится в Functions: будущая модель должна быть version-pinned library/snippet surface, который разворачивается платформой на publish в проверяемый source snapshot.

Такой shared code будет частью Functions, если он остаётся маленьким, версионированным, укладывается в source contract и загружается текущим runtime до активации. Полноценные npm dependencies, generated bundles, assets, WASM/native addons, WebSocket, свой server lifecycle, subprocess/FFI/raw sockets и большие dependency graphs останутся Compute или Static workloads.

export const config = {
name: "hello",
} as const;
export default {
async fetch(request, ctx) {
const url = new URL(request.url);
await ctx.log.info("hello", {
path: url.pathname,
invocationId: ctx.invocation.id,
});
return Response.json({
ok: true,
path: url.pathname,
workspace: ctx.invocation.workspaceId,
});
},
};

HTTP-привязка функций объявляется не в export const config, а в onreza.rules.toml через pipeline action. Для path conditions используйте структурированные exact, prefix или glob.

ctx содержит:

Поле Описание
ctx.env Environment bindings текущего окружения. Функция на preview видит preview-переменные, на production — production. Используйте вместо process.env/Bun.env.
ctx.kv Key-value хранилище окружения: get, put/setttl), incr, delete, list. get возвращает Uint8Array | null; строковые значения декодируйте через TextDecoder.
ctx.sql Binding управляемого PostgreSQL как tagged template: await ctx.sql\select 1`иctx.sql.batch([…]). Доступен, когда к проекту привязана ровно одна активная база; иначе возвращает customer-safe ошибку not_attached, ambiguous_attachmentилиunavailable`.
ctx.invocation id, workspaceId, deploymentId, releaseId, functionName, revisionId и тип события. releaseId равен null для test-invoke без активного environment release.
ctx.log Structured function logs: debug, info, warn, error.
ctx.locals Объект для передачи данных между шагами одного route pipeline.
ctx.waitUntil(promise) Дождаться короткого side effect после формирования response.

Pipeline выполняет Functions вокруг Static/Compute или другой terminal-функции. Порядок и cache boundary задаются в onreza.rules.toml, а каждый шаг реализует соответствующий handler: request, response, observe или fetch.

[[rules]]
id = "dashboard-auth"
condition.path = { type = "prefix", value = "/dashboard" }
action = { type = "pipeline", steps = [
{ use = "require-session", mode = "request", failure = "closed" },
{ handle = "@app" },
{ use = "add-user-header", mode = "response", failure = "open" },
] }
export default {
async request(request, ctx) {
const session = request.headers.get("cookie")?.match(/session=([^;]+)/)?.[1];
if (!session) {
return Response.redirect(new URL("/login", request.url), 307);
}
const userBytes = await ctx.kv.get(`session:${session}`);
const user = userBytes ? new TextDecoder().decode(userBytes) : null;
if (!user) {
return Response.redirect(new URL("/login", request.url), 307);
}
ctx.locals.user = user;
return request;
},
async response(response, ctx) {
const user = typeof ctx.locals.user === "string" ? ctx.locals.user : "anonymous";
response.headers.set("x-user", user);
return response;
},
};

Если request step возвращает Response, pipeline короткозамыкает downstream. Если возвращает Request, edge продолжает выполнение с обновлённым request. Для каждого function step задаётся поведение при сбое/паузе через failure:

  • closed (по умолчанию для request) — запрос блокируется. Безопасный дефолт: auth-гейт не должен молча пропускать трафик.
  • open (по умолчанию для response/observe) — step пропускается, запрос идёт в downstream. Подходит для некритичной логики (аналитика, заголовки, A/B), но не для авторизации.

cache_position = "before" выполняет step до cache boundary, а "after" — после. Для auth-гейтов используйте failure = "closed" и before-cache позицию.

ctx.kv — хранилище уровня окружения. Сессия, записанная route pipeline, читается другими функциями в том же окружении. Между окружениями данные изолированы: preview не видит production. KV-операции тарифицируются отдельно от вызовов и ограничены лимитом плана.

const encoder = new TextEncoder();
const decoder = new TextDecoder();
await ctx.kv.set("user:42", JSON.stringify(profile), { ttl: 3600 });
await ctx.kv.put("avatar:42", encoder.encode("binary-safe value"), { ttl: 3600 });
const raw = await ctx.kv.get("user:42");
const storedProfile = raw ? JSON.parse(decoder.decode(raw)) : null;
const page = await ctx.kv.list({ prefix: "user:", limit: 100 });
await ctx.kv.delete("user:42");
// Атомарный счётчик — для rate limiting
const count = await ctx.kv.incr(`rate:${ip}`, { ttl: 60 });
if (count > 100) return new Response("Too Many Requests", { status: 429 });

Гарантии (важно для auth и лимитов):

  • read-your-writes в пределах региона — запись сразу видна на чтении в том же регионе;
  • между регионами — eventual с небольшим окном распространения. Отзыв сессии не мгновенный глобально → для безопасности дополняйте коротким ttl токена;
  • incr атомарен в пределах региона; глобальные rate-лимиты считайте приблизительными per-region;
  • переполнение хранилища окружения — запись падает с ошибкой, чужие ключи не вытесняются (никаких внезапных разлогинов). Истечение — только по ttl.

Если нужна строгая глобальная консистентность — это не KV, используйте внешнюю БД.

В onreza.toml нет секции для ONREZA Functions. Суффикс entry-файла не настраивается намеренно, чтобы сканер искал только branded files, а не обходил проект как generic TypeScript indexer. Конфигурация функции живёт рядом с handler в самом entry-файле:

export const config = {
name: "api-webhook",
} as const;

nrz deploy является публичным release-путём Functions. CLI парсит top-of-file config, собирает один bounded self-contained source file и до отправки запускает каждый entrypoint точным подписанным runtime текущего STABLE release. Та же проверка доступна отдельно через nrz functions check.

Платформа повторно проверяет только форму payload: пути, размеры, literal config, отсутствие файлов вне source snapshot и Edge Rules. Она не поддерживает второй список разрешённых Bun.* или node:*: совместимость кода определяет runtime, который только что загрузил функцию в CLI. nrz deploy активирует функции только вместе с успешным app release. Публичного functions-only publish path в beta нет; nrz rules publish может отдельно обновить Edge Rules, не отправляя function source.

Список найденных entry-файлов имеет replace-семантику. Если удалить или переименовать *.nrz-fn.*, прежняя функция перестанет быть активной только после успешного deploy. Edge Rule, который всё ещё ссылается на удалённое имя, остановит публикацию до переключения live release.

Rollback восстанавливает точный Functions snapshot выбранного release. Функции, появившиеся только в более новой версии, после переключения не остаются активными.

Wire schema для publish payload генерируется из shared contract: /schemas/onreza-functions-publish-payload-v1.schema.json. Source contract beta фиксирует maxFilesPerFunction = 1, maxUserImportDepth = 0 и 128 KB на entry source file.

onreza.rules.toml используется для static edge rules и HTTP route pipeline: redirects, rewrites, headers, cache, firewall и привязки Functions к маршрутам. Если publish payload не содержит edgeRules, платформа сохраняет текущие пользовательские rules окружения. Набор автоматических adapter rules принадлежит deployment snapshot целиком: adapter, отсутствующий в новой сборке, не переносится в новый release. Чтобы очистить пользовательскую часть, нужно опубликовать явный пустой rules = [].

Эффективные runtime limits берутся из плана и platform policy. Значения выше лимита плана не применяются.

Доступная поверхность Bun.*, node:*, process.* и Web API встроена в конкретный Functions runtime release. Она не описывается отдельным статическим allowlist в API платформы: такой список быстро расходился бы с исполняемым кодом.

Стабильная модель для авторинга:

  • используйте стандартные Web API для request/response, streams, URL и crypto;
  • используйте доступные pure utility API Bun и node:*, когда их принимает nrz functions check;
  • environment, KV, SQL, logs и invocation metadata получайте через ctx;
  • outbound HTTP выполняйте через обычный fetch;
  • не рассчитывайте на файловую систему, subprocess, listener/raw sockets, FFI, native addons, package installation, nested workers или process control.

Точный ответ для установленного release даёт сам runtime:

Окно терминала
nrz functions check .

Команда скачивает и проверяет подписанный STABLE artifact, затем действительно загружает каждый найденный entrypoint. nrz deploy --dry и обычный nrz deploy проходят тот же preflight. Если API отсутствует или import нельзя загрузить, публикация останавливается локально с диагностикой runtime.

Source contract остаётся отдельным ограничением транспорта: v1 не включает local helper files, npm dependencies, assets или direct linking между функциями, даже если обычный Bun умеет их загружать.

Limit Hobby Pro Enterprise
Concurrent invocations 4 32 256
Memory per worker 256 MB 1 GB 4 GB
CPU per invocation 250 ms 1000 ms 4000 ms
Wall timeout 10 s 60 s 300 s
Pending queue 16 256 1024
Warm retention (макс.) 5 s 30 s 300 s
Response body 4 MB 10 MB 50 MB
Source entry files / function 1 1 1
Source entry file size 128 KB 128 KB 128 KB

Function logs отправляются в обычный deployment log pipeline и доступны на странице деплоя в блоке ONREZA Functions. Каждая запись сохраняет function identity, revision/layer context в beta, invocation id, runtime и уровень лога.

Runtime errors в beta мапятся в короткие customer-safe категории:

Runtime code Что означает
FUNCTION_BACKPRESSURE Очередь invocation заполнена или concurrency cap достигнут.
FUNCTION_TIMEOUT Invocation превысил wall timeout.
FUNCTION_POLICY_VIOLATION Runtime или внешний sandbox остановил недоступную capability во время invocation. Обычная несовместимость API должна обнаруживаться CLI preflight до публикации.
FUNCTION_HANDLER_ERROR Handler выбросил ошибку или вернул не Response.
FUNCTION_DISABLED Функция приостановлена (kill switch или авто-защита: error-loop, рекурсия, превышение объёма).
PLATFORM_ERROR Runtime path не смог завершить invocation.
INVALID_INVOCATION Платформа не смогла доставить валидный invocation в function host.
POOL_ENTRYPOINT_MISMATCH Старая host generation была отклонена после деплоя или смены config.
UNSUPPORTED_EVENT_TYPE Тип события не поддерживается текущим beta runtime path; используйте HTTP fetch handlers.
WORKSPACE_BOUNDARY_VIOLATION Invocation не прошёл workspace isolation boundary.

Функции дешёвые и self-serve, поэтому платформа защищает вас от runaway-расходов per-функция и per-окружение:

  • если функция стабильно ошибается, зацикливает вызовы (например, дёргает собственный route) или превышает безопасный объём — срабатывает защита: если сбой связан с недавней публикацией, платформа сперва откатывается на прошлую рабочую revision; иначе функция приостанавливается в этом окружении, перестаёт тарифицироваться, и вы получаете уведомление;
  • приостановка одной функции на preview никогда не затрагивает production — указатели независимы;
  • приостановленная функция отвечает FUNCTION_DISABLED; восстановление — автоматически по cooldown или вручную;
  • вы можете в любой момент сами отключить функцию или отдельную route pipeline привязку.

Перед активацией можно прогнать функцию test-запросом в реальном sandbox, не переключая live-трафик — правьте логику (особенно auth) без риска на проде.

  • Public beta обслуживает HTTP fetch handlers. Queue и scheduled event shapes зарезервированы в runtime protocol, но не являются стабильным customer API.
  • Relative/local helper imports не являются публичным v1 API. Runtime hydrator оставляет seam для будущих version-pinned publish-time snippets/libraries, но customer-authored source сейчас self-contained.
  • Публичные релизы Functions выполняются через nrz-cli; dashboard source editor и прямой publish API не являются отдельными release-authorities.
  • Route pipeline (request/response/observe вокруг @app или terminal-функции) — часть продуктовой модели Functions и замена прежним edge middleware. Сейчас стабильный публичный handler — HTTP fetch.
  • Streaming responses в v1 буферизуются и ограничены response body cap.
  • ctx.kv — core binding модели Functions (основной способ хранить state для auth/session/RBAC). ctx.sql подключается к управляемому PostgreSQL, когда у проекта есть ровно одна active attachment; без attachment, при нескольких attachment или пока БД не active binding возвращает customer-safe ошибку. ctx.queue и ctx.bucket пока зарезервированы и не являются стабильным customer API.
  • Billing в beta использует один public overage meter — BFU-hour. Runtime лимиты остаются защитными лимитами, а per-function counters используются для diagnostics и COGS attribution.

В репозитории есть минимальный пример:

  • examples/onreza-functions/hello-world