{ type: "node" }

Всё есть Node

Единая рекурсивная JSON-структура для всего. Страницы, таблицы, контент и файлы.

Протокол Coreless

Строгая, читаемая JSON-схема. Каждый объект следует единому интерфейсу Node.

Рекурсивная структура

Папки содержат дочерние элементы. Дочерние элементы могут быть папками. Бесконечная вложенность.

Строгая типизация

У каждого узла есть определенный тип и предсказуемая схема данных.

# Интерфейс узла Coreless
interface Node {
  type: "folder" | "text" | "image" | "table";
  name: string;
  slug: string; // kebab-case
  data: Record<string, any>;
  children?: Node[];
}

Нативные типы узлов

Встроенные примитивы для создания любой модели контента без сложных миграций.

folder

{ children: [...] }

text

{ data: { content: "..." } }

image

{ data: { url: "...", size: 1024 } }

table

{ data: { fields: [...] }, children: [row] }

row

{ data: { [slug]: value } }

article

{ data: { content: "# Markdown..." } }

Универсальная архитектура

Будь то лендинг (папки и блоки) или база знаний (таблицы и строки) — это просто дерево узлов.

Структура лендинга

folder (landing)
├─ folder (hero)
│ ├─ text (title)
│ └─ image (cover)
└─ folder (about)

Структура базы знаний

folder (docs)
├─ table (categories)
└─ table (articles)
  └─ row (article_1)

Импорт / Экспорт JSON

Поскольку вся структура — это просто дерево JSON, вы можете экспортировать состояние проекта в файл, закоммитить в Git или мгновенно импортировать в новую среду.

npm-пакет

coreless-lib — официальный клиент

Библиотека на npm (TypeScript / JavaScript). Оборачивает тот же HTTP API: от статического HTML до Next.js App Router. React подключается по желанию.

  • Ядро без привязки к фреймворку — импорт из coreless-lib или coreless-lib/core
  • ContentClient: .text(), .image(), .video(), .file(), .list(), .table(), .field(), .node() по slash-пути
  • Таблицы: offset/limit, exclude_fields, search, filter — уходят в query при fetch
  • Дополнительно на Coreless: getNode(path), getRow(page, table, row), getTable(page, table, params)
  • useContent(slug) для CSR; в RSC — await new Coreless(...).getPage(slug) в Server Component
  • Peer dependency: react — опционально; нужен только для CorelessProvider / useContent

Установка

npm install coreless-lib

Класс Coreless

HttpContentFetcher дергает GET /content/{projectSlug}/{path}. Coreless.getPage() возвращает ContentClient с хелперами по путям.

import { Coreless } from 'coreless-lib';

const cms = new Coreless({
  apiHost: process.env.NEXT_PUBLIC_API_URL!,
  projectSlug: 'my-project',
});

const page = await cms.getPage('home');
page.text('hero/title');
page.table('pricing/plans', { offset: 0, limit: 10 });

React

CorelessProvider + HttpContentFetcher в корне; useContent(slug) даёт те же методы плюс loading, error и refresh().

import { CorelessProvider, HttpContentFetcher, useContent } from 'coreless-lib';

const fetcher = new HttpContentFetcher({
  apiHost: 'https://api.example.com',
  projectSlug: 'my-project',
});

// <CorelessProvider fetcher={fetcher}>…</CorelessProvider>

function Home() {
  const c = useContent('home');
  if (c.loading) return <p>…</p>;
  return <h1>{c.text('hero/title')}</h1>;
}
API

Как приложение читает контент

Та же логика, что в проекте → API docs: одно JSON-дерево на страницу и типизированные хелперы по путям полей.

Пример: slug проекта acme-studio, страница home — пути совпадают со slug папок и блоков в дереве.

REST

Эндпоинт отдаёт дерево страницы. Тот же GET, что использует админка (api.nodes.getContent).

GET /content/acme-studio/home
Authorization: Bearer <access_token>
Accept: application/json

Типизированный доступ

После cms.getPage(slug страницы) — .text(), .image(), .table()… с путями вроде hero/title, как в автогенерации в API docs.

import { cms } from '@/lib/cms';

const page = await cms.getPage('home');

const headline = page.text('hero/title');
// string | undefined

const lead = page.text('hero/lead');
// string | undefined

const cover = page.image('hero/cover');
// { url, width?, height?, filename?, size?, content_type? } | undefined

const pricing = page.table('sections/pricing');
// { fields: [...], rows: { _id, _slug, ...data }[] } | undefined

Запрос из фронтенда

Прямой fetch с тем же URL и заголовками, что в lib/api.ts.

import { getHeaders } from '@/lib/api';

const API_URL = process.env.NEXT_PUBLIC_API_URL;

const res = await fetch(
  `${API_URL}/content/acme-studio/home`,
  { headers: getHeaders() }
);
const tree = await res.json();