chore: Move to Typescript (#2783)

This PR moves the entire project to Typescript. Due to the ~1000 ignores this will lead to a messy codebase for a while, but the churn is worth it – all of those ignore comments are places that were never type-safe previously.

closes #1282
This commit is contained in:
Tom Moor
2021-11-29 06:40:55 -08:00
committed by GitHub
parent 25ccfb5d04
commit 15b1069bcc
1017 changed files with 17410 additions and 54942 deletions

View File

@@ -1,8 +1,8 @@
// @flow
import { onAuthenticatePayload } from "@hocuspocus/server";
import { Document } from "@server/models";
import { getUserForJWT } from "@server/utils/jwt";
import { AuthenticationError } from "../errors";
import { Document } from "../models";
import policy from "../policies";
import { getUserForJWT } from "../utils/jwt";
const { can } = policy;
@@ -11,26 +11,26 @@ export default class Authentication {
connection,
token,
documentName,
}: {
connection: { readOnly: boolean },
token: string,
documentName: string,
}) {
}: onAuthenticatePayload) {
// allows for different entity types to use this multiplayer provider later
const [, documentId] = documentName.split(".");
if (!token) {
throw new AuthenticationError("Authentication required");
throw AuthenticationError("Authentication required");
}
const user = await getUserForJWT(token);
if (user.isSuspended) {
throw new AuthenticationError("Account suspended");
throw AuthenticationError("Account suspended");
}
const document = await Document.findByPk(documentId, { userId: user.id });
const document = await Document.findByPk(documentId, {
userId: user.id,
});
if (!can(user, "read", document)) {
throw new AuthenticationError("Authorization required");
throw AuthenticationError("Authorization required");
}
// set document to read only for the current user, thus changes will not be

View File

@@ -1,22 +0,0 @@
// @flow
import Logger from "../logging/logger";
import { User } from "../models";
export default class CollaborationLogger {
async onLoadDocument(data: {
documentName: string,
context: { user: User },
}) {
Logger.info("hocuspocus", `Loaded document "${data.documentName}"`, {
userId: data.context.user.id,
});
}
async onConnect(data: { documentName: string, context: { user: User } }) {
Logger.info("hocuspocus", `New connection to "${data.documentName}"`);
}
async onDisconnect(data: { documentName: string, context: { user: User } }) {
Logger.info("hocuspocus", `Connection to "${data.documentName}" closed `);
}
}

View File

@@ -0,0 +1,22 @@
import {
onConnectPayload,
onDisconnectPayload,
onLoadDocumentPayload,
} from "@hocuspocus/server";
import Logger from "@server/logging/logger";
export default class CollaborationLogger {
async onLoadDocument(data: onLoadDocumentPayload) {
Logger.info("hocuspocus", `Loaded document "${data.documentName}"`, {
userId: data.context.user.id,
});
}
async onConnect(data: onConnectPayload) {
Logger.info("hocuspocus", `New connection to "${data.documentName}"`);
}
async onDisconnect(data: onDisconnectPayload) {
Logger.info("hocuspocus", `Connection to "${data.documentName}" closed `);
}
}

View File

@@ -1,21 +1,15 @@
// @flow
import { onChangePayload, onLoadDocumentPayload } from "@hocuspocus/server";
import { debounce } from "lodash";
import * as Y from "yjs";
import Logger from "@server/logging/logger";
import { Document, User } from "@server/models";
import documentUpdater from "../commands/documentUpdater";
import Logger from "../logging/logger";
import { Document, User } from "../models";
import markdownToYDoc from "./utils/markdownToYDoc";
const DELAY = 3000;
export default class Persistence {
async onLoadDocument({
documentName,
...data
}: {
documentName: string,
document: Y.Doc,
}) {
async onLoadDocument({ documentName, ...data }: onLoadDocumentPayload) {
const [, documentId] = documentName.split(".");
const fieldName = "default";
@@ -40,23 +34,20 @@ export default class Persistence {
);
const ydoc = markdownToYDoc(document.text, fieldName);
const state = Y.encodeStateAsUpdate(ydoc);
await document.update({ state: Buffer.from(state) }, { hooks: false });
await document.update(
{
state: Buffer.from(state),
},
{
hooks: false,
}
);
return ydoc;
}
onChange = debounce(
async ({
document,
context,
documentName,
}: {
document: Y.Doc,
context: { user: ?User },
documentName: string,
}) => {
async ({ document, context, documentName }: onChangePayload) => {
const [, documentId] = documentName.split(".");
Logger.info("database", `Persisting ${documentId}`);
try {

View File

@@ -1,66 +0,0 @@
// @flow
import Metrics from "../logging/metrics";
export default class Tracing {
onLoadDocument({
documentName,
instance,
}: {
documentName: string,
instance: any,
}) {
Metrics.increment("collaboration.load_document", { documentName });
Metrics.gaugePerInstance(
"collaboration.documents_count",
instance.getDocumentsCount()
);
}
onAuthenticationFailed({ documentName }: { documentName: string }) {
Metrics.increment("collaboration.authentication_failed", { documentName });
}
onConnect({
documentName,
instance,
}: {
documentName: string,
instance: any,
}) {
Metrics.increment("collaboration.connect", { documentName });
Metrics.gaugePerInstance(
"collaboration.connections_count",
instance.getConnectionsCount()
);
}
onDisconnect({
documentName,
instance,
}: {
documentName: string,
instance: any,
}) {
Metrics.increment("collaboration.disconnect", { documentName });
Metrics.gaugePerInstance(
"collaboration.connections_count",
instance.getConnectionsCount()
);
Metrics.gaugePerInstance(
"collaboration.documents_count",
// -1 adjustment because hook is called before document is removed
instance.getDocumentsCount() - 1
);
}
onChange({ documentName }: { documentName: string }) {
Metrics.increment("collaboration.change", { documentName });
}
onDestroy() {
Metrics.gaugePerInstance("collaboration.connections_count", 0);
Metrics.gaugePerInstance("collaboration.documents_count", 0);
}
}

View File

@@ -0,0 +1,60 @@
import {
onChangePayload,
onConnectPayload,
onDisconnectPayload,
onLoadDocumentPayload,
} from "@hocuspocus/server";
import Metrics from "@server/logging/metrics";
export default class Tracing {
onLoadDocument({ documentName, instance }: onLoadDocumentPayload) {
Metrics.increment("collaboration.load_document", {
documentName,
});
Metrics.gaugePerInstance(
"collaboration.documents_count",
instance.getDocumentsCount()
);
}
onAuthenticationFailed({ documentName }: { documentName: string }) {
Metrics.increment("collaboration.authentication_failed", {
documentName,
});
}
onConnect({ documentName, instance }: onConnectPayload) {
Metrics.increment("collaboration.connect", {
documentName,
});
Metrics.gaugePerInstance(
"collaboration.connections_count",
instance.getConnectionsCount()
);
}
onDisconnect({ documentName, instance }: onDisconnectPayload) {
Metrics.increment("collaboration.disconnect", {
documentName,
});
Metrics.gaugePerInstance(
"collaboration.connections_count",
instance.getConnectionsCount()
);
Metrics.gaugePerInstance(
"collaboration.documents_count", // -1 adjustment because hook is called before document is removed
instance.getDocumentsCount() - 1
);
}
onChange({ documentName }: onChangePayload) {
Metrics.increment("collaboration.change", {
documentName,
});
}
onDestroy() {
Metrics.gaugePerInstance("collaboration.connections_count", 0);
Metrics.gaugePerInstance("collaboration.documents_count", 0);
}
}

View File

@@ -1,13 +1,12 @@
// @flow
import { Node, Fragment } from "prosemirror-model";
import { parser, schema } from "rich-markdown-editor";
import { prosemirrorToYDoc } from "y-prosemirror";
import * as Y from "yjs";
import embeds from "../../../shared/embeds";
import embeds from "@shared/embeds";
export default function markdownToYDoc(
markdown: string,
fieldName?: string = "default"
fieldName = "default"
): Y.Doc {
let node = parser.parse(markdown);
@@ -16,9 +15,11 @@ export default function markdownToYDoc(
// on the server we need to mimic this behavior.
function urlsToEmbeds(node: Node): Node {
if (node.type.name === "paragraph") {
// @ts-expect-error ts-migrate(2339) FIXME: Property 'content' does not exist on type 'Fragmen... Remove this comment to see the full error message
for (const textNode of node.content.content) {
for (const embed of embeds) {
if (textNode.text && embed.matcher(textNode.text)) {
// @ts-expect-error ts-migrate(2322) FIXME: Type 'ProsemirrorNode<Schema<never, never>> | null... Remove this comment to see the full error message
return schema.nodes.embed.createAndFill({
href: textNode.text,
});
@@ -29,6 +30,7 @@ export default function markdownToYDoc(
if (node.content) {
const contentAsArray =
// @ts-expect-error ts-migrate(2339) FIXME: Property 'content' does not exist on type 'Fragmen... Remove this comment to see the full error message
node.content instanceof Fragment ? node.content.content : node.content;
node.content = Fragment.fromArray(contentAsArray.map(urlsToEmbeds));
}
@@ -37,6 +39,5 @@ export default function markdownToYDoc(
}
node = urlsToEmbeds(node);
return prosemirrorToYDoc(node, fieldName);
}