# Создание функции — текущий 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`): ```js await requestJSON(API_BASE + '/functions', 'POST', { name, language, entrypoint, route, methods, timeout, code: document.getElementById('c-code').value // <-- всегда текст }); ``` ### Форма редактирования (`edit-modal`) Аналогична. `submitEdit()` шлёт: ```js await requestJSON(API_BASE + '/functions/' + name + '/code', 'PUT', { code: document.getElementById('e-code').value, timeout: ... }); ``` --- ## Backend ### POST /console/api/functions → `handleCreateFunction` Структура запроса (`model.CreateFunctionRequest`): ```json { "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` ```json { "code": "...", "timeout": 60 } ``` Обязателен: `code`. Алгоритм: 1. Читает текущую Function → берёт `fission-console/language` из аннотации 2. `buildDeployArchive(lang, code)` → байты 3. Создаёт **новый Package** с именем `{name}-pkg-{timestamp_base36}` 4. Обновляет Function.spec.package.packageref на новый пакет 5. Удаляет старый Package (best-effort) > Почему новый Package, а не обновление старого: > executor кэширует function-service по `functionUid` — не видит изменений в том же Package. > Новое имя гарантирует cache miss. --- ## Линтер (ai/check) и LLM (ai/ask) ### POST /console/api/ai/check ```json { "language": "python", "code": "def main(): ..." } ``` Работает **только со строкой кода**. Запускает: - python: `python3 -m py_compile ` - nodejs: `node --check ` - ruby: `ruby -c ` - php: `php -l ` - 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 (как сейчас) - режим «Архив» — ``, 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 — гибрид (рекомендован)** 1. Фронт: при выборе архива — использовать JSZip, найти entrypoint-файл по языку, показать его в readonly-textarea, кнопки линтера и LLM работают с этим текстом. 2. Отправка: `archive_b64` (весь zip) идёт на бэк как есть. 3. Бэк (опционально): после сохранения прогнать `decodeArchiveBytesToSource` → линтер, вернуть предупреждение если нашёл ошибки. Так пользователь видит и код и результат линтера до деплоя, и полный архив уходит без изменений.