You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Tiptap Hocuspocus协作编辑:页面刷新内容丢失及数据格式疑问

问题:Hocuspocus Database扩展刷新后无法加载已编辑内容及数据格式疑问

问题描述

  • 单个用户编辑文档时,页面刷新后(并非每次出现),Hocuspocus的Database扩展无法加载已编辑内容。按预期该扩展应在页面刷新时恢复之前的编辑内容,若理解有误请指正。
  • 额外疑问:是否需要手动将数据转换为Uint8Array,还是Database扩展默认会处理该转换?

原代码

import * as dotenv from "dotenv";
dotenv.config();

import { Server } from "@hocuspocus/server";
import { Database } from "@hocuspocus/extension-database";
import mysql from "mysql2";
/* 

是否需要手动转换为Uint8Array,还是DB扩展默认会处理?

*/

//import { toUint8Array, fromUint8Array } from "js-base64";
import { Throttle } from "@hocuspocus/extension-throttle";

const pool = mysql.createPool({
    connectionLimit: 100,
    host: process.env.DB_HOST,
    user: process.env.DB_USERNAME,
    password: process.env.DB_PASSWORD,
    database: process.env.DB_DATABASE,
    port: process.env.DB_PORT,
    debug: false,
});

const server = Server.configure({
    port: process.env.HOCUSPOCUS_PORT,
    extensions: [
        new Throttle({
            throttle: 200,
            banTime: 1,
        }),

        new Database({
            fetch: async ({ documentName }) => {
                return new Promise((resolve, reject) => {
                    console.log("Trying to fetch");
                    pool?.query(
                        "SELECT data FROM ydocuments WHERE name = ? ORDER BY id DESC",
                        [documentName],
                        (error, row) => {
                            if (error) {
                                reject(error);
                            }
                            console.log("data: " + JSON.stringify(row));
                            if (row && row.data) {
                                resolve(row.data);
                            } else {
                                resolve(null);
                            }
                        },
                    );
                });
            },

            store: async ({ documentName, state }) => {
                pool?.query(
                    "INSERT INTO ydocuments (name, data) VALUES (?, ?) ON DUPLICATE KEY UPDATE data = ?",
                    [documentName, state, state],
                    (error, result) => {
                        if (error) {
                            throw error;
                        }
                        console.log(`inserted/updated ${result.affectedRows}`);
                    },
                );
            },
        }),
    ],
});

server.listen();

解决方案

一、刷新加载失败的核心问题及修复

  1. 查询结果处理错误
    mysql2的query方法返回的结果是数组(即使只有一条匹配记录),原代码中row.data永远是undefined,导致每次都返回null。需要取数组的第一个元素:rows[0]?.data。

  2. 异步存储未正确等待
    原store方法中使用回调式的pool.query,但没有返回Promise,Hocuspocus可能在数据未写入完成时就结束流程,导致存储失败。改用mysql2的Promise版可以解决这个问题。

  3. 表结构约束缺失
    ON DUPLICATE KEY UPDATE需要name字段设置唯一约束,否则每次都会插入新记录,而不是更新现有记录。确保ydocuments表的name字段添加UNIQUE索引。

二、Uint8Array转换问题

不需要手动转换,Database扩展和mysql2会自动处理:

  • 如果数据库data字段类型是BLOB/LONGBLOB,mysql2会自动将Uint8Array转为Buffer存储,读取时返回Buffer(Buffer是Uint8Array的子类,Hocuspocus可以直接识别)。
  • 如果用TEXT类型存储,才需要手动将Uint8Array转成base64字符串存储,读取时再转回Uint8Array,但推荐用BLOB类型存储二进制数据,效率更高。

修复后的代码

import * as dotenv from "dotenv";
dotenv.config();

import { Server } from "@hocuspocus/server";
import { Database } from "@hocuspocus/extension-database";
import mysql from "mysql2/promise"; // 改用Promise版mysql2
import { Throttle } from "@hocuspocus/extension-throttle";

const pool = mysql.createPool({
    connectionLimit: 100,
    host: process.env.DB_HOST,
    user: process.env.DB_USERNAME,
    password: process.env.DB_PASSWORD,
    database: process.env.DB_DATABASE,
    port: process.env.DB_PORT,
    debug: false,
});

const server = Server.configure({
    port: process.env.HOCUSPOCUS_PORT,
    extensions: [
        new Throttle({
            throttle: 200,
            banTime: 1,
        }),

        new Database({
            fetch: async ({ documentName }) => {
                console.log("Trying to fetch");
                // 用await处理异步查询,结果rows是数组
                const [rows] = await pool.query(
                    "SELECT data FROM ydocuments WHERE name = ? ORDER BY id DESC LIMIT 1",
                    [documentName]
                );
                console.log("data: " + JSON.stringify(rows));
                // 返回第一条记录的data,没有则返回null
                return rows[0]?.data || null;
            },

            store: async ({ documentName, state }) => {
                console.log("Storing document:", documentName);
                // 用await确保存储完成
                const [result] = await pool.query(
                    "INSERT INTO ydocuments (name, data) VALUES (?, ?) ON DUPLICATE KEY UPDATE data = ?",
                    [documentName, state, state]
                );
                console.log(`inserted/updated ${result.affectedRows}`);
            },
        }),
    ],
});

server.listen();

内容的提问来源于stack exchange,提问作者Karthik

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.26 23:52:01