KV store

В предыдущей статье мы настроили наш проект и развернули приложение «Hello World» на Cloudflare Workers. Теперь мы рассмотрим хранение и получение наших данных в KV-хранилище Cloudflare Workers. Это простое, но полезное хранилище ключевых значений, и у него есть щедрый бесплатный уровень, который мы можем использовать для нашего проекта. Начнем с установки нескольких зависимостей:

npm install -D @cloudflare/workers-types @miniflare/kv @miniflare/storage-memory
Войти в полноэкранный режим Выход из полноэкранного режима

@cloudflare/workers-types предоставляет глобальные определения типов для KV API. Мы добавим его в наш файл tsconfig.json в compilerOptions.types:

{
    "compilerOptions": {
        // ... existing compiler options ...
-       "types": ["vite/client"]
+       "types": ["vite/client", "@cloudflare/workers-types"]
    }
}
Вход в полноэкранный режим Выход из полноэкранного режима

KV API доступен только на Cloudflare Workers. Но во время разработки Rakkas запускает наше приложение на Node.js. К счастью, в проекте Miniflare есть реализация KV для Node. Два других пакета, которые мы установили (@miniflare/kv и @miniflare/storage-memory) — это то, что нам нужно, чтобы иметь возможность использовать KV API во время разработки. Давайте создадим файл src/kv-mock.ts и создадим локальное хранилище KV для хранения постов нашего ublog («twits») во время тестирования:

import { KVNamespace } from "@miniflare/kv";
import { MemoryStorage } from "@miniflare/storage-memory";

export const postStore = new KVNamespace(new MemoryStorage());

const MOCK_POSTS = [
    {
        key: "1",
        content: "Hello, world!",
        author: "Jane Doe",
        postedAt: "2022-08-10T14:34:00.000Z",
    },
    {
        key: "2",
        content: "Hello ublog!",
        author: "Cody Reimer",
        postedAt: "2022-08-10T13:27:00.000Z",
    },
    {
        key: "3",
        content: "Wow, this is pretty cool!",
        author: "Zoey Washington",
        postedAt: "2022-08-10T12:00:00.000Z",
    },
];

// We'll add some mock posts
// Rakkas supports top level await
await Promise.all(
    // We'll do this in parallel with Promise.all,
    // just to be cool.
    MOCK_POSTS.map((post) =>
        postStore.put(post.key, post.content, {
            metadata: {
                author: post.author,
                postedAt: post.postedAt,
            },
        })
    )
);
Вход в полноэкранный режим Выход из полноэкранного режима

Как вы можете видеть, мы также добавили некоторые имитационные данные, потому что наше приложение еще не имеет функции «создать пост». Таким образом, мы можем начать получать и показывать некоторые посты до того, как реализуем эту функцию.

Метод put магазина принимает ключ, значение и некоторые необязательные метаданные. Мы будем использовать значение для хранения фактического содержимого поста, а метаданные — для хранения автора и даты создания поста. Ключ должен быть уникальным, но кроме этого он в настоящее время не имеет смысла, мы вернемся к этому позже.

Теперь мы должны сделать это хранилище доступным для серверного кода нашего приложения. Лучшее место для этого — запись HatTip, которая является основной серверной точкой входа в приложение Rakkas. Это необязательный файл, который не входит в сгенерированный шаблон, поэтому мы добавим его вручную как src/entry-hattip.ts:

import { createRequestHandler } from "rakkasjs";

declare module "rakkasjs" {
    interface ServerSideLocals {
        postStore: KVNamespace;
    }
}

export default createRequestHandler({
    middleware: {
        beforePages: [
            async (ctx) => {
                if (import.meta.env.DEV) {
                    const { postStore } = await import("./kv-mock");
                    ctx.locals.postStore = postStore;
                } else {
                    ctx.locals.postStore = (ctx.platform as any).env.KV_POSTS;
                }
            },
        ],
    },
});
Вход в полноэкранный режим Выход из полноэкранного режима

Ух ты, как много незнакомого. Давайте разберемся.

Предполагается, что запись HatTip по умолчанию экспортирует обработчик запроса HatTip. Поэтому мы создаем его с помощью createRequestHandler. createRequestHandler принимает кучу опций для настройки поведения сервера. Одна из них — middleware, которая используется для внедрения функций промежуточного ПО в конвейер обработки запросов Rakkas. Посредники HatTip во многом схожи с посредниками Express. Поэтому концепция должна быть вам знакома, если вы раньше использовали Express.

Мы добавляем наше промежуточное ПО до того, как Rakkas обработает страницы нашего приложения (beforePages). Фактически, это самая ранняя точка перехвата. В промежуточном ПО мы внедряем наш магазин в объект контекста запроса, который будет доступен серверному коду нашего приложения. Объект контекста запроса имеет свойство locals, предназначенное для хранения специфических для приложения вещей, таких как это.

Часть, начинающаяся с declare module "rakkasjs" — это техника TypeScript для расширения интерфейсов, объявленных в других модулях. В данном случае мы расширяем интерфейс ServerSideLocals, который является типом ctx.locals, где ctx — объект контекста запроса.

import.meta.env.DEV — это функция Vite. Ее значение true во время разработки и false в производстве. Здесь мы используем его, чтобы определить, следует ли нам создать макет KV store или использовать настоящий магазин на Cloudflare Workers.

Для производства адаптер Cloudflare Workers от HatTip делает так называемые привязки доступными в ctx.platform.env. Тип ctx.platformunknown, потому что он меняется в зависимости от окружения. Поэтому мы используем as any, чтобы угодить компилятору TypeScript. KV_POSTS — это просто имя, которое мы выбрали для привязки имени нашего хранилища постов.

Благодаря этому довольно простому промежуточному ПО, KV-хранилище, в котором будут храниться наши посты, будет доступно нашему приложению как ctx.locals.postStore, где ctx — контекст запроса.

Получение данных из хранилища KV

Теперь мы запустим dev-сервер с помощью npm run dev и отредактируем файл src/pages/index.page.tsx, чтобы получить и отобразить наши макеты постов. В Rakkas есть очень классный хук для выборки данных, который называется useServerSideQuery. С помощью этого хука вы можете поместить код на стороне сервера прямо в ваши компоненты без необходимости создавать конечные точки API:

import { useServerSideQuery } from "rakkasjs";

export default function HomePage() {
    const { data: posts } = useServerSideQuery(async (ctx) => {
        // This callback always runs on the server.
        // So we have access to the request context!

        // Get a list of the keys and metadata
        const list = await ctx.locals.postStore.list<{
            author: string;
            postedAt: string;
        }>();

        // Get individual posts and move things around
        // a little to make it easier to render
        const posts = await Promise.all(
            list.keys.map((key) =>
                ctx.locals.postStore
                    .get(key.name)
                    .then((data) => ({ key, content: data }))
            )
        );

        return posts;
    });

    return (
        <main>
            <h1>Posts</h1>
            <ul>
                {posts.map((post) => (
                    <li key={post.key.name}>
                        <div>{post.content}</div>
                        <div>
                            {/* post.key.metadata may not be available while testing for */}
                            {/* reasons we'll cover later. That's why we need the nullish */}
                            {/* checks here */}
                            <i>{post.key.metadata?.author ?? "Unknown author"}</i>
                            &nbsp;
                            <span>
                                {post.key.metadata
                                    ? new Date(post.key.metadata.postedAt).toLocaleString()
                                    : "Unknown date"}
                            </span>
                        </div>
                        <hr />
                    </li>
                ))}
            </ul>
        </main>
    );
}
Вход в полноэкранный режим Выход из полноэкранного режима

Вот и все! Теперь при посещении сайта http://localhost:5173 вы должны увидеть список макетов постов. Пока не беспокойтесь об уродливом внешнем виде. Мы рассмотрим стилизацию позже.

Сборка для производства

Теперь создайте приложение для производства и разверните его:

npm run build
npm run deploy
Вход в полноэкранный режим Выход из полноэкранного режима

К сожалению, если вы посетите ваш производственный URL сейчас, вы получите ошибку. Это потому, что мы еще не создали KV store на Cloudflare Workers. Мы сделаем это с помощью wrangler CLI::

npx wrangler kv:namespace create KV_POSTS
Войти в полноэкранный режим Выйти из полноэкранного режима

Если все прошло успешно, вы должны увидеть сообщение, подобное этому:

Add the following to your configuration file in your kv_namespaces array:
{ binding = "KV_POSTS", id = "<YOUR_KV_NAMESPACE_ID>" }
Вход в полноэкранный режим Выйти из полноэкранного режима

Мы сделаем именно это и добавим следующее в конец нашего файла wrangler.toml:

[[kv_namespaces]]
binding = "KV_POSTS"
id = "<YOUR_KV_NAMESPACE_ID>"
Войти в полноэкранный режим Выйти из полноэкранного режима

Затем мы снова выполним развертывание с помощью npm run deploy. На этот раз ошибка исчезла, но мы по-прежнему не увидим ни одного сообщения. Давайте добавим несколько с помощью wrangler CLI:

npx wrangler kv:key put --binding KV_POSTS 1 "Hello world!"
npx wrangler kv:key put --binding KV_POSTS 2 "Ooh! Pretty nice!"
npx wrangler kv:key put --binding KV_POSTS 3 "Wrangler lets us add new values to KV!"
Войти в полноэкранный режим Выйти из полноэкранного режима

К сожалению, wrangler CLI не позволяет нам добавлять метаданные к нашим постам, поэтому мы увидим «Неизвестный автор» и «Неизвестная дата» в пользовательском интерфейсе, но кроме этого… ЭТО РАБОТАЕТ, УРА! У нас есть рабочее хранилище данных для нашего приложения!

Вы также можете посетить Cloudflare Dashboard и перейти в раздел Workers > KV, чтобы добавить/удалить/отредактировать значения в вашем хранилище. Если вы это сделаете, то заметите, что Cloudflare использует тот же механизм KV для хранения статических активов.

Очистка

Если вы собираетесь разместить свой код в публичном репозитории, вам не следует раскрывать идентификатор вашего хранилища KV. Просто сделайте копию вашего wrangler.toml в виде wrangler.example.toml и удалите из копии идентификатор KV store. Затем добавьте wrangler.toml в свой .gitignore и выполните git rm wrangler.toml --cached перед фиксацией. Я не совсем уверен, что это необходимо, но в прошлом произошла утечка данных, связанная с идентификатором магазина KV, так что лучше перестраховаться.

Что дальше?

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

Прогресс до этого момента вы можете найти на GitHub.

Оцените статью
devanswers.ru
Добавить комментарий