Skip to content

Ваш первый модуль

Мы соберём минимальный модуль, который монтирует страницу в оболочке Building OS и читает данные через контекст платформы. Бюджет: 30 минут.

1. Создание каркаса

SDK создаёт весь модуль за вас — манифест, конфиг Vite с уже подключённым экспортом федерации, Shell.tsx и package.json:

bash
npx @tv/extension-sdk init-module hello \
  --name="Hello Module" \
  --category=operations
✓ Scaffolded module at ./tv-module-hello
  Files written:
    module-manifest.json
    vite.config.ts
    src/Shell.tsx
    package.json

--category обязателен и должен быть одним из: core, operations, engagement, infrastructure, analytics, ai. Если фронтенд у вас уже есть и нужен только манифест — используйте init-manifest.

Хотите сначала посмотреть на настоящий модуль?

modules/tv-module-example — каноническая эталонная реализация: федерация инструментов Copilot, проверка запросов по HMAC-подписи, отчёт о живости. В каталоге модулей перечислены 30 модулей, работающих на платформе сегодня.

2. Разберитесь с манифестом

Генератор пишет валидный стартовый манифест. Вот его форма с заполненными полями, которые вы реально будете править:

json
{
  "$schema": "./node_modules/@tv/extension-sdk/manifest.schema.json",
  "sdkVersion": "1.1.0",
  "id": "@acme/module-hello",
  "name": "Hello Module",
  "description": "Приветствует оператора и перечисляет помещения активного здания.",
  "version": "1.0.0",
  "minCoreVersion": ">=2.0.0",
  "category": "operations",
  "buildingTypes": ["all"],
  "capabilities": { "provides": [], "requires": [] },
  "permissions": [
    {
      "subject": "building.spaces",
      "actions": ["read"],
      "reason": "Выводит список помещений на главной странице модуля."
    }
  ],
  "events": { "publishes": [], "subscribes": [] },
  "mcpTools": [],
  "ui": {
    "remoteEntry": "./Shell",
    "routes": [{ "path": "/hello" }],
    "navigation": [
      { "label": "Hello", "icon": "Hand", "path": "/hello", "section": "operations" }
    ]
  },
  "lifecycle": {
    "healthEndpoint": "/health",
    "init": "on_demand",
    "dependencies": []
  }
}

Несколько полей значат больше, чем кажется по их размеру:

  • id — ваша собственная npm-область, не @tv. Генератор по умолчанию ставит @tv/…; поменяйте.
  • permissions[].reason — показывается дословно администратору, который устанавливает ваш модуль. Пишите для него, а не для себя.
  • ui.remoteEntry — должно остаться ./Shell. Оболочка ищет ровно это имя; tv-sdk check-exposes это проверяет.
  • lifecycle.initon_demand загружает модуль лениво, при обращении к его маршруту. on_boot используйте, только если оболочка действительно не может стартовать без вас.

Все поля описаны в справочнике по манифесту.

3. Проверьте его

bash
npx @tv/extension-sdk validate module-manifest.json
✓ module-manifest.json is a valid module manifest.
  id:      @acme/module-hello
  version: 1.0.0
  core:    >=2.0.0

Если он неверен, валидатор укажет точное поле:

✗ permissions[0].subject: must be one of [building.spaces, building.elements, ...]

Встройте обе проверки в CI, чтобы сломанный контракт никогда не покидал вашу машину:

yaml
# .github/workflows/manifest.yml
- run: npx @tv/extension-sdk validate module-manifest.json
- run: npx @tv/extension-sdk check-exposes module-manifest.json --config=./vite.config.ts

Автодополнение в редакторе

Начиная с SDK 1.1.0 публикуемый manifest.schema.json пригоден для проверки в редакторе: укажите $schema на установленную копию, и редактор будет подсказывать поля и подсвечивать ошибки прямо при вводе:

json
{
  "$schema": "./node_modules/@tv/extension-sdk/manifest.schema.json",
  "sdkVersion": "1.1.0",
  ...
}

tv-sdk init-module добавляет эту строку за вас. В версиях 1.0.x схема генерировалась в режиме output и отклоняла манифесты, которые tv-sdk validate принимает, — если вы закреплены ниже 1.1.0, не указывайте $schema.

Авторитетной проверкой остаётся tv-sdk validate — именно её выполняют CI и реестр.

4. Соберите фронтенд

Ваш модуль экспортирует федеративный Shell. Внутри него используйте контекст платформы:

tsx
// src/Shell.tsx
import { usePlatformContext, useBuilding } from '@tv/extension-sdk/react';
import { useQuery } from '@tanstack/react-query';

export default function Shell() {
  const { api } = usePlatformContext();
  const building = useBuilding();

  const { data: spaces } = useQuery({
    queryKey: ['spaces', building.id],
    queryFn: () => api.get<Space[]>(`/api/v1/buildings/${building.id}/spaces`),
  });

  return (
    <div>
      <h1>Hello from {building.name}</h1>
      <p>{spaces?.length ?? 0} spaces</p>
    </div>
  );
}

Никаких токенов, никаких URL, которые нужно собирать вручную. Клиент api предварительно аутентифицирован и ограничен областью активного арендатора.

5. (Опционально) Объявите возможность бэкенда

Если у вашего модуля есть бэкенд на NestJS:

ts
import { ModuleCapability, RequiresLicense } from '@tv/extension-sdk/nestjs';

@ModuleCapability({ id: 'hello.greeting', version: '1.0.0' })
@Controller('api/v1/buildings/:buildingId/hello')
export class HelloController {
  @RequiresLicense('@acme/module-hello')
  @Get()
  greet() {
    return { message: 'hello' };
  }
}

6. Протестируйте на мок-контексте

tsx
import { createMockPlatformContext } from '@tv/extension-sdk/testing';
import { PlatformProvider } from '@tv/extension-sdk/react';
import { render, screen } from '@testing-library/react';
import Shell from './Shell';

const ctx = createMockPlatformContext({
  building: {
    id: 'b1',
    slug: 'demo-mall',
    name: 'Demo Mall',
    type: 'mall',
    timezone: 'Europe/Moscow',
  },
});

render(
  <PlatformProvider value={ctx}>
    <Shell />
  </PlatformProvider>,
);

expect(screen.getByText(/Demo Mall/)).toBeInTheDocument();

Вы тестируете на точно той же форме контекста, что используется в продакшене — просто заполненной фейковыми данными. Никаких сюрпризов «работает у меня, ломается в проде».

7. Запустите его в песочнице

Перед публикацией прогоните модуль на настоящем (изолированном) здании:

bash
npx @tv/extension-sdk sandbox create \
  --name="hello-dev" --type=mall --storeys=3 --area-sqm=20000

Создать песочницу — полный жизненный цикл.

Что вы только что узнали

  • tv-sdk init-module даёт заведомо рабочую форму; не собирайте её вручную.
  • Манифест — это ваш контракт; validate и check-exposes должны быть в CI.
  • PlatformContext — ваша единственная дверь к состоянию платформы.
  • Мок-контекст делает тесты соответствующими продакшену.

Далее: разберитесь с PlatformContext и событиями.

Создано на платформе Tango Vision. Вопросы? developers@tango.vision