migrate from github

This commit is contained in:
2026-07-15 22:49:51 +08:00
commit 81bc0cecbe
30 changed files with 2580 additions and 0 deletions
+63
View File
@@ -0,0 +1,63 @@
const Y = require('yjs');
const { keyCollabDocUpdates } = require('../key');
function findDocInMemory(docs, docId) {
if (!docs) {
return null;
}
return docs.get(`doc-${docId}`) || docs.get(docId) || null;
}
async function buildDocFromRedis(redis, docId) {
const redisKey = keyCollabDocUpdates(docId);
const updates = await redis.getListBuffers(redisKey);
if (!updates || updates.length === 0) {
return null;
}
const doc = new Y.Doc();
for (const update of updates) {
if (update && update.length > 0) {
Y.applyUpdate(doc, update);
}
}
return doc;
}
async function loadDocForRead(docs, redis, docId) {
const inMemoryDoc = findDocInMemory(docs, docId);
if (inMemoryDoc) {
return inMemoryDoc;
}
return buildDocFromRedis(redis, docId);
}
const XML_FRAGMENT_DOC_TYPES = new Set(['yoresee_rich_text']);
function encodeSnapshotResponse(doc, docType) {
const update = Y.encodeStateAsUpdate(doc);
let content = '';
if (XML_FRAGMENT_DOC_TYPES.has(docType)) {
const fragment = doc.getXmlFragment('content');
content = fragment ? fragment.toString() : '';
} else {
const ytext = doc.getText('content');
content = ytext ? ytext.toString() : '';
}
return {
state: Buffer.from(update).toString('base64'),
content
};
}
function encodeBinaryUpdate(doc) {
return Buffer.from(Y.encodeStateAsUpdate(doc));
}
module.exports = {
loadDocForRead,
encodeSnapshotResponse,
encodeBinaryUpdate
};
+20
View File
@@ -0,0 +1,20 @@
function writeJson(res, status, payload) {
res.writeHead(status, { 'Content-Type': 'application/json' });
res.end(JSON.stringify(payload));
}
function writeText(res, status, body) {
res.writeHead(status);
res.end(body);
}
function getRequestURL(req) {
const host = req.headers && req.headers.host ? req.headers.host : 'localhost';
return new URL(req.url || '/', `http://${host}`);
}
module.exports = {
writeJson,
writeText,
getRequestURL
};
+45
View File
@@ -0,0 +1,45 @@
const { writeJson } = require('./helpers');
function buildReadinessState(isDraining, redisClient) {
const ready = !isDraining && !!redisClient;
return {
ready,
redisState: redisClient ? 'connected' : 'disconnected',
detail: isDraining ? 'server is draining' : undefined
};
}
function handleProbeRequest(pathname, res, { isDraining, redisClient }) {
if (pathname === '/livez') {
writeJson(res, 200, { status: 'ok' });
return true;
}
const state = buildReadinessState(isDraining, redisClient);
if (pathname === '/readyz') {
writeJson(res, state.ready ? 200 : 503, {
status: state.ready ? 'ok' : 'not_ready',
redis: state.redisState,
detail: state.detail
});
return true;
}
if (pathname === '/health') {
writeJson(res, 200, {
status: 'ok',
redis: state.redisState,
persistence: 'custom',
ready: state.ready ? 'true' : 'false',
detail: state.detail
});
return true;
}
return false;
}
module.exports = {
handleProbeRequest
};
+77
View File
@@ -0,0 +1,77 @@
const { writeJson, writeText, getRequestURL } = require('./helpers');
const { handleProbeRequest } = require('./probes');
const { loadDocForRead, encodeSnapshotResponse, encodeBinaryUpdate } = require('./docs');
function decodeDocID(pathname, prefix) {
try {
return decodeURIComponent(pathname.slice(prefix.length));
} catch (_err) {
return '';
}
}
function createRequestHandler({ redis, wsUtils, getIsDraining }) {
const docs = wsUtils.docs;
return async function handleRequest(req, res) {
try {
const url = getRequestURL(req);
const pathname = url.pathname;
if (handleProbeRequest(pathname, res, { isDraining: getIsDraining(), redisClient: redis.redisClient })) {
return;
}
if (pathname === '/api/active-rooms') {
const rooms = await redis.getActiveRooms();
writeJson(res, 200, { rooms, count: rooms.length });
return;
}
if (pathname.startsWith('/internal/yjs/doc-snapshot/')) {
const docId = decodeDocID(pathname, '/internal/yjs/doc-snapshot/');
if (!docId) {
writeText(res, 400, 'doc id required');
return;
}
const docType = url.searchParams.get('type') || '';
const doc = await loadDocForRead(docs, redis, docId);
if (!doc) {
writeText(res, 404, 'doc not loaded');
return;
}
writeJson(res, 200, encodeSnapshotResponse(doc, docType));
return;
}
if (pathname.startsWith('/internal/yjs/doc/')) {
const docId = decodeDocID(pathname, '/internal/yjs/doc/');
if (!docId) {
writeText(res, 400, 'doc id required');
return;
}
const doc = await loadDocForRead(docs, redis, docId);
if (!doc) {
writeText(res, 404, 'doc not loaded');
return;
}
res.writeHead(200, { 'Content-Type': 'application/octet-stream' });
res.end(encodeBinaryUpdate(doc));
return;
}
writeText(res, 404, '');
} catch (err) {
console.error('Request handling failed:', err);
writeText(res, 500, 'internal error');
}
};
}
module.exports = {
createRequestHandler
};
+18
View File
@@ -0,0 +1,18 @@
const http = require('http');
const { createRequestHandler } = require('./router');
function createHTTPServer({ redis, wsUtils, getIsDraining }) {
const requestHandler = createRequestHandler({
redis,
wsUtils,
getIsDraining
});
return http.createServer((req, res) => {
void requestHandler(req, res);
});
}
module.exports = {
createHTTPServer
};