7.5 KiB
Создание функции — текущий flow (май 2026)
UI (браузер)
Форма создания (create-modal)
Поля в index.html:
c-lang— select (python / nodejs / go / ruby / php)c-name— имя функцииc-entry— entrypoint (заполняется автоматически по языку изLANG_TEMPLATES)c-route— HTTP-путьc-methods— методы, через запятуюc-timeout— таймаут в секундахc-schedule-enabled+c-cron— cron-расписание (опционально)c-code— textarea с исходным кодом функции (единственный способ передать код)
Кнопки рядом с c-code:
aiCheck(...)— линтер (POST/console/api/ai/check)aiExplain(...)— объяснение через LLM (POST/console/api/ai/ask)
При нажатии «Создать» вызывается submitCreate() (js/functions.js):
await requestJSON(API_BASE + '/functions', 'POST', {
name, language, entrypoint, route, methods, timeout,
code: document.getElementById('c-code').value // <-- всегда текст
});
Форма редактирования (edit-modal)
Аналогична. submitEdit() шлёт:
await requestJSON(API_BASE + '/functions/' + name + '/code', 'PUT', {
code: document.getElementById('e-code').value,
timeout: ...
});
Backend
POST /console/api/functions → handleCreateFunction
Структура запроса (model.CreateFunctionRequest):
{ "name": "fn", "language": "python", "code": "def main(): ...",
"entrypoint": "main", "route": "/ns/fn", "methods": ["GET"],
"timeout": 60, "ttl": "" }
Обязательные: name, language (или environment), code.
Путь для интерпретируемых языков (python / nodejs / ruby / php):
code (строка)
→ buildDeployArchive(lang, code)
python/ruby/php: []byte(code) (raw, не zip)
nodejs: BuildJSDeployZip(code) → zip с package.json + main.js
→ buildDeploySpec(ctx, bytes)
→ uploadToStoragesvc() → multipart POST → storagesvc → archiveURL
→ fallback: base64 literal (если storagesvc недоступен)
→ Package.spec.deployment = { type: "url", url: "..." }
или = { type: "literal", literal: "base64..." }
Путь для Go:
code
→ BuildGoSourceZip(code) → zip с исходником
→ uploadToStoragesvc → archiveURL
→ Package.spec.source = { ... } (source package, компилируется builder job)
Package.spec.buildcommand = "build"
После создания Package → создаётся Function → HTTPTrigger. При ошибке на любом шаге — откат (best-effort DELETE предыдущих объектов).
PUT /console/api/functions/{name}/code → handleUpdateFunctionCode
{ "code": "...", "timeout": 60 }
Обязателен: code.
Алгоритм:
- Читает текущую Function → берёт
fission-console/languageиз аннотации buildDeployArchive(lang, code)→ байты- Создаёт новый Package с именем
{name}-pkg-{timestamp_base36} - Обновляет Function.spec.package.packageref на новый пакет
- Удаляет старый Package (best-effort)
Почему новый Package, а не обновление старого: executor кэширует function-service по
functionUid— не видит изменений в том же Package. Новое имя гарантирует cache miss.
Линтер (ai/check) и LLM (ai/ask)
POST /console/api/ai/check
{ "language": "python", "code": "def main(): ..." }
Работает только со строкой кода. Запускает:
- python:
python3 -m py_compile <tmpfile.py> - nodejs:
node --check <tmpfile.js> - ruby:
ruby -c <tmpfile.rb> - php:
php -l <tmpfile.php> - go:
go/parser.ParseFile(...)прямо в процессе (без tmpfile)
Временный файл создаётся в os.TempDir(), удаляется через defer.
POST /console/api/ai/ask
Отправляет код в LLM (объяснение, анализ). Детали в ai_prompts.go.
Что сейчас НЕ поддерживается
- Загрузка архива (.zip) из файла — ни в UI, ни в API
- Архив можно только скачать (через
extractPackageSourceCode→fetchPackageArchive)
Planned: загрузка архива
API
Добавить в CreateFunctionRequest и UpdateCodeRequest поле archive_b64 string.
Логика в handler: если archive_b64 != "" — пропустить buildDeployArchive,
сразу uploadToStoragesvc(base64.Decode(archive_b64)).
Валидация:
- magic bytes
PK\x03\x04(zip) - максимум 10MB (против 1MB для кода)
UI
Раздельные режимы в форме — кнопки-переключатели «✏️ Код» / «📦 Архив»:
- режим «Код» — textarea (как сейчас)
- режим «Архив» —
<input type="file" accept=".zip">, textarea скрыта
В submitCreate / submitEdit — FileReader.readAsDataURL(file) → base64 → поле archive_b64.
Линтер и LLM для архива
Проблема: линтер (ai/check) и LLM (ai/ask) принимают code — строку.
Архив — zip из нескольких файлов. Варианты:
Вариант A — на бэке, при загрузке
После распаковки архива (уже есть decodeArchiveBytesToSource в package.go):
- Извлечь главный файл (entrypoint)
- Прогнать через линтер
- Результат вернуть в том же ответе на create/update (поле
lint_result)
Плюсы: не меняет контракт линтера, нет доп. round-trip. Минусы: линтится только один файл из архива.
Вариант B — на фронте, до отправки
FileReader.readAsText(file) — не работает для zip напрямую.
Можно использовать JSZip (npm/CDN) — распаковать в браузере → взять главный файл → передать в ai/check.
Плюсы: линтинг до загрузки, быстрая обратная связь. Минусы: добавляется зависимость (JSZip ~100KB).
Вариант C — гибрид (рекомендован)
- Фронт: при выборе архива — использовать JSZip, найти entrypoint-файл по языку, показать его в readonly-textarea, кнопки линтера и LLM работают с этим текстом.
- Отправка:
archive_b64(весь zip) идёт на бэк как есть. - Бэк (опционально): после сохранения прогнать
decodeArchiveBytesToSource→ линтер, вернуть предупреждение если нашёл ошибки.
Так пользователь видит и код и результат линтера до деплоя, и полный архив уходит без изменений.