fix: Collaboration debounce shared between docs (#3401)

* fix: Collaboration debounce shared between docs

* Rename, Tracing -> Metrics

* Add tracing

* tsc

* fix: Lock document row when loading document in collaboration service incase state needs writing

* fix: Incorrect service name regression
This commit is contained in:
Tom Moor
2022-04-16 14:58:17 -07:00
committed by GitHub
parent 1a8f2c3bb0
commit 4c4b80ba9b
9 changed files with 192 additions and 156 deletions

View File

@@ -51,9 +51,9 @@
"@dnd-kit/core": "^4.0.3",
"@dnd-kit/modifiers": "^4.0.0",
"@dnd-kit/sortable": "^5.1.0",
"@getoutline/y-prosemirror": "^1.0.16",
"@hocuspocus/provider": "^1.0.0-alpha.21",
"@hocuspocus/server": "^1.0.0-alpha.78",
"@getoutline/y-prosemirror": "^1.0.18",
"@hocuspocus/provider": "^1.0.0-alpha.36",
"@hocuspocus/server": "^1.0.0-alpha.102",
"@outlinewiki/koa-passport": "^4.1.4",
"@outlinewiki/passport-azure-ad-oauth2": "^0.1.0",
"@renderlesskit/react": "^0.6.0",
@@ -200,7 +200,7 @@
"winston": "^3.3.3",
"ws": "^7.5.3",
"y-indexeddb": "^9.0.6",
"yjs": "^13.5.12"
"yjs": "^13.5.34"
},
"devDependencies": {
"@babel/cli": "^7.10.5",

View File

@@ -1,10 +1,14 @@
import { onAuthenticatePayload } from "@hocuspocus/server";
import { onAuthenticatePayload, Extension } from "@hocuspocus/server";
import { APM } from "@server/logging/tracing";
import Document from "@server/models/Document";
import { can } from "@server/policies";
import { getUserForJWT } from "@server/utils/jwt";
import { AuthenticationError } from "../errors";
export default class Authentication {
@APM.trace({
spanName: "authentication",
})
export default class AuthenticationExtension implements Extension {
async onAuthenticate({
connection,
token,

View File

@@ -2,13 +2,14 @@ import {
onConnectPayload,
onDisconnectPayload,
onLoadDocumentPayload,
Extension,
} from "@hocuspocus/server";
import Logger from "@server/logging/logger";
export default class CollaborationLogger {
export default class LoggerExtension implements Extension {
async onLoadDocument(data: onLoadDocumentPayload) {
Logger.info("hocuspocus", `Loaded document "${data.documentName}"`, {
userId: data.context.user.id,
userId: data.context.user?.id,
});
}
@@ -17,6 +18,8 @@ export default class CollaborationLogger {
}
async onDisconnect(data: onDisconnectPayload) {
Logger.info("hocuspocus", `Connection to "${data.documentName}" closed `);
Logger.info("hocuspocus", `Closed connection to "${data.documentName}"`, {
userId: data.context.user?.id,
});
}
}

View File

@@ -3,11 +3,12 @@ import {
onConnectPayload,
onDisconnectPayload,
onLoadDocumentPayload,
Extension,
} from "@hocuspocus/server";
import Metrics from "@server/logging/metrics";
export default class Tracing {
onLoadDocument({ documentName, instance }: onLoadDocumentPayload) {
export default class MetricsExtension implements Extension {
async onLoadDocument({ documentName, instance }: onLoadDocumentPayload) {
Metrics.increment("collaboration.load_document", {
documentName,
});
@@ -23,7 +24,7 @@ export default class Tracing {
});
}
onConnect({ documentName, instance }: onConnectPayload) {
async onConnect({ documentName, instance }: onConnectPayload) {
Metrics.increment("collaboration.connect", {
documentName,
});
@@ -33,7 +34,7 @@ export default class Tracing {
);
}
onDisconnect({ documentName, instance }: onDisconnectPayload) {
async onDisconnect({ documentName, instance }: onDisconnectPayload) {
Metrics.increment("collaboration.disconnect", {
documentName,
});
@@ -47,13 +48,13 @@ export default class Tracing {
);
}
onChange({ documentName }: onChangePayload) {
async onStoreDocument({ documentName }: onChangePayload) {
Metrics.increment("collaboration.change", {
documentName,
});
}
onDestroy() {
async onDestroy() {
Metrics.gaugePerInstance("collaboration.connections_count", 0);
Metrics.gaugePerInstance("collaboration.documents_count", 0);
}

View File

@@ -0,0 +1,86 @@
import {
onStoreDocumentPayload,
onLoadDocumentPayload,
Extension,
} from "@hocuspocus/server";
import invariant from "invariant";
import * as Y from "yjs";
import { sequelize } from "@server/database/sequelize";
import Logger from "@server/logging/logger";
import { APM } from "@server/logging/tracing";
import Document from "@server/models/Document";
import documentUpdater from "../commands/documentUpdater";
import markdownToYDoc from "./utils/markdownToYDoc";
@APM.trace({
spanName: "persistence",
})
export default class PersistenceExtension implements Extension {
async onLoadDocument({ documentName, ...data }: onLoadDocumentPayload) {
const [, documentId] = documentName.split(".");
const fieldName = "default";
// Check if the given field already exists in the given y-doc. This is import
// so we don't import a document fresh if it exists already.
if (!data.document.isEmpty(fieldName)) {
return;
}
return await sequelize.transaction(async (transaction) => {
const document = await Document.scope("withState").findOne({
transaction,
lock: transaction.LOCK.UPDATE,
where: {
id: documentId,
},
});
invariant(document, "Document not found");
if (document.state) {
const ydoc = new Y.Doc();
Logger.info("database", `Document ${documentId} is in database state`);
Y.applyUpdate(ydoc, document.state);
return ydoc;
}
Logger.info(
"database",
`Document ${documentId} is not in state, creating from markdown`
);
const ydoc = markdownToYDoc(document.text, fieldName);
const state = Y.encodeStateAsUpdate(ydoc);
await document.update(
{
state: Buffer.from(state),
},
{
hooks: false,
transaction,
}
);
return ydoc;
});
}
async onStoreDocument({
document,
context,
documentName,
}: onStoreDocumentPayload) {
const [, documentId] = documentName.split(".");
Logger.info("database", `Persisting ${documentId}`);
try {
await documentUpdater({
documentId,
ydoc: document,
userId: context.user?.id,
});
} catch (err) {
Logger.error("Unable to persist document", err, {
documentId,
userId: context.user?.id,
});
}
}
}

View File

@@ -1,77 +0,0 @@
import { onChangePayload, onLoadDocumentPayload } from "@hocuspocus/server";
import invariant from "invariant";
import { debounce } from "lodash";
import * as Y from "yjs";
import Logger from "@server/logging/logger";
import Document from "@server/models/Document";
import documentUpdater from "../commands/documentUpdater";
import markdownToYDoc from "./utils/markdownToYDoc";
const DELAY = 3000;
export default class Persistence {
async onLoadDocument({ documentName, ...data }: onLoadDocumentPayload) {
const [, documentId] = documentName.split(".");
const fieldName = "default";
// Check if the given field already exists in the given y-doc. This is import
// so we don't import a document fresh if it exists already.
if (!data.document.isEmpty(fieldName)) {
return;
}
const document = await Document.scope("withState").findOne({
where: {
id: documentId,
},
});
invariant(document, "Document not found");
if (document.state) {
const ydoc = new Y.Doc();
Logger.info("database", `Document ${documentId} is in database state`);
Y.applyUpdate(ydoc, document.state);
return ydoc;
}
Logger.info(
"database",
`Document ${documentId} is not in state, creating from markdown`
);
const ydoc = markdownToYDoc(document.text, fieldName);
const state = Y.encodeStateAsUpdate(ydoc);
await document.update(
{
state: Buffer.from(state),
},
{
hooks: false,
}
);
return ydoc;
}
onChange = debounce(
async ({ document, context, documentName }: onChangePayload) => {
const [, documentId] = documentName.split(".");
Logger.info("database", `Persisting ${documentId}`);
try {
await documentUpdater({
documentId,
ydoc: document,
userId: context.user?.id,
});
} catch (err) {
Logger.error("Unable to persist document", err, {
documentId,
userId: context.user?.id,
});
}
},
DELAY,
{
maxWait: DELAY * 3,
}
);
}

View File

@@ -10,7 +10,7 @@ if (process.env.DD_API_KEY) {
// SOURCE_COMMIT is used by Docker Hub
// SOURCE_VERSION is used by Heroku
version: process.env.SOURCE_COMMIT || process.env.SOURCE_VERSION,
service: "outline",
service: process.env.DD_SERVICE || "outline",
},
{
useMock: process.env.NODE_ENV === "test",

View File

@@ -4,26 +4,28 @@ import { Server } from "@hocuspocus/server";
import invariant from "invariant";
import Koa from "koa";
import WebSocket from "ws";
import AuthenticationExtension from "../collaboration/authentication";
import LoggerExtension from "../collaboration/logger";
import PersistenceExtension from "../collaboration/persistence";
import TracingExtension from "../collaboration/tracing";
import AuthenticationExtension from "../collaboration/AuthenticationExtension";
import LoggerExtension from "../collaboration/LoggerExtension";
import MetricsExtension from "../collaboration/MetricsExtension";
import PersistenceExtension from "../collaboration/PersistenceExtension";
export default function init(app: Koa, server: http.Server) {
const path = "/collaboration";
const wss = new WebSocket.Server({
noServer: true,
});
const hocuspocus = Server.configure({
debounce: 3000,
maxDebounce: 10000,
extensions: [
new AuthenticationExtension(),
// @ts-expect-error ts-migrate(2322) FIXME: Type 'Persistence' is not assignable to type 'Exte... Remove this comment to see the full error message
new PersistenceExtension(),
new LoggerExtension(),
// @ts-expect-error ts-migrate(2322) FIXME: Type 'Persistence' is not assignable to type 'Exte... Remove this comment to see the full error message
new TracingExtension(),
new MetricsExtension(),
],
});
server.on("upgrade", function (req, socket, head) {
if (req.url && req.url.indexOf(path) > -1) {
const documentName = url.parse(req.url).pathname?.split("/").pop();
@@ -34,7 +36,8 @@ export default function init(app: Koa, server: http.Server) {
});
}
});
server.on("shutdown", () => {
hocuspocus.destroy();
return hocuspocus.destroy();
});
}

126
yarn.lock
View File

@@ -1276,36 +1276,47 @@
dependencies:
tslib "^2.1.0"
"@getoutline/y-prosemirror@^1.0.16":
version "1.0.16"
resolved "https://registry.yarnpkg.com/@getoutline/y-prosemirror/-/y-prosemirror-1.0.16.tgz#34a29966208113bceb8c46bbf8417853e8885e09"
integrity sha512-rJ3NF3Qk9v1/dNKQ25k3aud7TcV8s5175U9stagCDdvwsI67JufgzaWhT0jBhX8tEaZPdQVmXoqe8bf6OC72Cg==
"@getoutline/y-prosemirror@^1.0.18":
version "1.0.18"
resolved "https://registry.yarnpkg.com/@getoutline/y-prosemirror/-/y-prosemirror-1.0.18.tgz#17245c0362d30adb85131c86fb9a59358884b234"
integrity sha512-nLxqUHEHJDBwbcMWhlPWlJ4VpdjtajkmKSAWeVTsIEa5HTo1JQSdnADdS/HFSVSkESW8b6TRrOJylyHDn46uYQ==
dependencies:
lib0 "^0.2.42"
"@hocuspocus/provider@^1.0.0-alpha.21":
version "1.0.0-alpha.21"
resolved "https://registry.yarnpkg.com/@hocuspocus/provider/-/provider-1.0.0-alpha.21.tgz#369869e33a7c138041a03bbb80acd61dd77e3632"
integrity sha512-24brZ0OIeUInbMBTN2weL06Xmt4KUn3Sj7BbNkxgt7QwHjIqpcqlZrHE8IYg46HTbp8k0KKnc1CCMxb1ui0fRA==
"@hocuspocus/common@^1.0.0-alpha.11":
version "1.0.0-alpha.11"
resolved "https://registry.yarnpkg.com/@hocuspocus/common/-/common-1.0.0-alpha.11.tgz#d598d221465338c1d912251105519e88f8486805"
integrity sha512-oOddSLUTr8KrC58KSs5YHzr99ZSI4HZdIkYRoqmrusViF8M850uLXgYce7eG7Xaq4KlvXCSDG+wioQRTFXkCaA==
dependencies:
"@lifeomic/attempt" "^3.0.0"
lib0 "^0.2.42"
lib0 "^0.2.47"
"@hocuspocus/provider@^1.0.0-alpha.36":
version "1.0.0-alpha.36"
resolved "https://registry.yarnpkg.com/@hocuspocus/provider/-/provider-1.0.0-alpha.36.tgz#a09dd42baa9c88cbd63027a18edcd3f79823a0cf"
integrity sha512-vmrbaS2Si408Gau1vv/xH7ln/QbVJtUZsgLz0DwYYWWGcAJffaU1f89B2+sOL8IGRLclybldrVfZR+tFKBMzRw==
dependencies:
"@hocuspocus/common" "^1.0.0-alpha.11"
"@lifeomic/attempt" "^3.0.2"
lib0 "^0.2.46"
y-protocols "^1.0.5"
yjs "^13.5.0"
yjs "^13.5.29"
"@hocuspocus/server@^1.0.0-alpha.78":
version "1.0.0-alpha.78"
resolved "https://registry.yarnpkg.com/@hocuspocus/server/-/server-1.0.0-alpha.78.tgz#06597ae871e3cfc68dd4fda5e6fbc696c8baf452"
integrity sha512-78HbOiJLo2b130UjJk7Z/Olue/qKBeL/CPn2B5/FrDRa41SClftAXipQTSa6J7JYbMi5tUyDP8cO2gckBC2Q3Q==
"@hocuspocus/server@^1.0.0-alpha.102":
version "1.0.0-alpha.102"
resolved "https://registry.yarnpkg.com/@hocuspocus/server/-/server-1.0.0-alpha.102.tgz#6c478032b3b30b45d96cbb744d8f61a6b9a71cf1"
integrity sha512-U82HAy9S9gNuPShsUrefJH2Bdv71+6gjIueNW39oLiWjR87Nmuenjzu1gbVcC6sJwjlsj3JJ0E1NDPu0xTDfxQ==
dependencies:
"@types/async-lock" "^1.1.2"
"@types/uuid" "^8.3.0"
"@types/ws" "^8.2.0"
async-lock "^1.2.8"
lib0 "^0.2.41"
"@hocuspocus/common" "^1.0.0-alpha.11"
"@types/async-lock" "^1.1.3"
"@types/uuid" "^8.3.4"
"@types/ws" "^8.5.3"
async-lock "^1.3.1"
kleur "^4.1.4"
lib0 "^0.2.46"
uuid "^8.3.2"
ws "^8.2.3"
yjs "^13.5.0"
ws "^8.5.0"
y-protocols "^1.0.5"
yjs "^13.5.29"
"@icons/material@^0.2.4":
version "0.2.4"
@@ -1802,10 +1813,10 @@
"@babel/runtime" "^7.7.2"
regenerator-runtime "^0.13.3"
"@lifeomic/attempt@^3.0.0":
version "3.0.0"
resolved "https://registry.yarnpkg.com/@lifeomic/attempt/-/attempt-3.0.0.tgz#75fecc204f8b0ac18b5363b4404bb32450f01859"
integrity sha512-Ibk4Vfl46dSrhtH5fHsrTA4waAuyP7/qcr3uo0mO70azRc6LWgJILlMy3B1oOvyiN9jQcdqwsThaQkPKLiYKTg==
"@lifeomic/attempt@^3.0.2":
version "3.0.3"
resolved "https://registry.yarnpkg.com/@lifeomic/attempt/-/attempt-3.0.3.tgz#e742a5b85eb673e2f1746b0f39cb932cbc6145bb"
integrity sha512-GlM2AbzrErd/TmLL3E8hAHmb5Q7VhDJp35vIbyPVA5Rz55LZuRr8pwL3qrwwkVNo05gMX1J44gURKb4MHQZo7w==
"@nicolo-ribaudo/chokidar-2@^2.1.8":
version "2.1.8"
@@ -2582,10 +2593,10 @@
dependencies:
"@types/node" "*"
"@types/async-lock@^1.1.2":
version "1.1.3"
resolved "https://registry.yarnpkg.com/@types/async-lock/-/async-lock-1.1.3.tgz#0d86017cf87abbcb941c55360e533d37a3f23b3d"
integrity sha512-UpeDcjGKsYEQMeqEbfESm8OWJI305I7b9KE4ji3aBjoKWyN5CTdn8izcA1FM1DVDne30R5fNEnIy89vZw5LXJQ==
"@types/async-lock@^1.1.3":
version "1.1.5"
resolved "https://registry.yarnpkg.com/@types/async-lock/-/async-lock-1.1.5.tgz#a82f33e09aef451d6ded7bffae73f9d254723124"
integrity sha512-A9ClUfmj6wwZMLRz0NaYzb98YH1exlHdf/cdDSKBfMQJnPOdO8xlEW0Eh2QsTTntGzOFWURcEjYElkZ1IY4GCQ==
"@types/babel__core@^7.0.0", "@types/babel__core@^7.1.14":
version "7.1.17"
@@ -3452,10 +3463,10 @@
resolved "https://registry.yarnpkg.com/@types/utf8/-/utf8-3.0.0.tgz#8f4875063d2ea966c57a34a25c11333520e83980"
integrity sha512-QrhvCktdm5wD48axAnjqSzPH9lOj0MiCYfMX6MSqGs2Jv+txwvdxviXiCEj8zSCWIEDU9SIJ7g9pU5KtxRgYSg==
"@types/uuid@^8.3.0":
version "8.3.1"
resolved "https://registry.yarnpkg.com/@types/uuid/-/uuid-8.3.1.tgz#1a32969cf8f0364b3d8c8af9cc3555b7805df14f"
integrity sha512-Y2mHTRAbqfFkpjldbkHGY8JIzRN6XqYRliG8/24FcHm2D2PwW24fl5xMRTVGdrb7iMrwCaIEbLWerGIkXuFWVg==
"@types/uuid@^8.3.4":
version "8.3.4"
resolved "https://registry.yarnpkg.com/@types/uuid/-/uuid-8.3.4.tgz#bd86a43617df0594787d38b735f55c805becf1bc"
integrity sha512-c/I8ZRb51j+pYGAu5CrFMRxqZ2ke4y2grEBO5AUjgSkSk+qT2Ea+OdWElz/OiMf5MNpn2b17kuVBwZLQJXzihw==
"@types/validator@*", "@types/validator@^13.7.1":
version "13.7.1"
@@ -3483,10 +3494,10 @@
anymatch "^3.0.0"
source-map "^0.6.0"
"@types/ws@^8.2.0":
version "8.2.0"
resolved "https://registry.yarnpkg.com/@types/ws/-/ws-8.2.0.tgz#75faefbe2328f3b833cb8dc640658328990d04f3"
integrity sha512-cyeefcUCgJlEk+hk2h3N+MqKKsPViQgF5boi9TTHSK+PoR9KWBb/C5ccPcDyAqgsbAYHTwulch725DV84+pSpg==
"@types/ws@^8.5.3":
version "8.5.3"
resolved "https://registry.yarnpkg.com/@types/ws/-/ws-8.5.3.tgz#7d25a1ffbecd3c4f2d35068d0b283c037003274d"
integrity sha512-6YOoWjruKj1uLf3INHH7D3qTXwFfEsg1kf3c0uDdSBJwfa/llkwIjrAGV7j7mVgGNbzTQ3HiHKKDXl6bJPD97w==
dependencies:
"@types/node" "*"
@@ -4130,10 +4141,10 @@ async-each@^1.0.1:
resolved "https://registry.yarnpkg.com/async-each/-/async-each-1.0.3.tgz#b727dbf87d7651602f06f4d4ac387f47d91b0cbf"
integrity sha512-z/WhQ5FPySLdvREByI2vZiTWwCnF0moMJ1hK9YQwDTHKh6I7/uSckMetoRGb5UBZPC1z0jlw+n/XCgjeH7y1AQ==
async-lock@^1.2.8:
version "1.3.0"
resolved "https://registry.yarnpkg.com/async-lock/-/async-lock-1.3.0.tgz#0fba111bea8b9693020857eba4f9adca173df3e5"
integrity sha512-8A7SkiisnEgME2zEedtDYPxUPzdv3x//E7n5IFktPAtMYSEAV7eNJF0rMwrVyUFj6d/8rgajLantbjcNRQYXIg==
async-lock@^1.3.1:
version "1.3.1"
resolved "https://registry.yarnpkg.com/async-lock/-/async-lock-1.3.1.tgz#f2301c200600cde97acc386453b7126fa8aced3c"
integrity sha512-zK7xap9UnttfbE23JmcrNIyueAn6jWshihJqA33U/hEnKprF/lVGBDsBv/bqLm2YMMl1DnpHhUY044eA0t1TUw==
async@0.9.x:
version "0.9.2"
@@ -9693,6 +9704,11 @@ kleur@^3.0.3:
resolved "https://registry.yarnpkg.com/kleur/-/kleur-3.0.3.tgz#a79c9ecc86ee1ce3fa6206d1216c501f147fc07e"
integrity sha512-eTIzlVOSUR+JxdDFepEYcBMtZ9Qqdef+rnzWdRZuMbOywu5tO2w2N7rqjoANZ5k9vywhL6Br1VRjUIgTQx4E8w==
kleur@^4.1.4:
version "4.1.4"
resolved "https://registry.yarnpkg.com/kleur/-/kleur-4.1.4.tgz#8c202987d7e577766d039a8cd461934c01cda04d"
integrity sha512-8QADVssbrFjivHWQU7KkMgptGTl6WAcSdlbBPY4uNF+mWr6DGcKrvY2w4FQJoXch7+fKMjj0dRrL75vk3k23OA==
koa-body@^4.2.0:
version "4.2.0"
resolved "https://registry.yarnpkg.com/koa-body/-/koa-body-4.2.0.tgz#37229208b820761aca5822d14c5fc55cee31b26f"
@@ -9975,10 +9991,10 @@ levn@~0.3.0:
prelude-ls "~1.1.2"
type-check "~0.3.2"
lib0@^0.2.35, lib0@^0.2.41, lib0@^0.2.42:
version "0.2.42"
resolved "https://registry.yarnpkg.com/lib0/-/lib0-0.2.42.tgz#6d8bf1fb8205dec37a953c521c5ee403fd8769b0"
integrity sha512-8BNM4MiokEKzMvSxTOC3gnCBisJH+jL67CnSnqzHv3jli3pUvGC8wz+0DQ2YvGr4wVQdb2R2uNNPw9LEpVvJ4Q==
lib0@^0.2.35, lib0@^0.2.42, lib0@^0.2.46, lib0@^0.2.47, lib0@^0.2.49:
version "0.2.49"
resolved "https://registry.yarnpkg.com/lib0/-/lib0-0.2.49.tgz#7addb5075063d66ea2c55749e5aeaa48e36278c8"
integrity sha512-ziwYLe/pmI9bjHsAehm4ApuVfZ+q+sbC+vO6Z5+KM+0Fe0MrTLwZSDkJ+cElnhFNQ0P6z/wVkRmc5+vTmImJ9A==
dependencies:
isomorphic.js "^0.2.4"
@@ -15390,10 +15406,10 @@ ws@^7.4.6, ws@^7.5.3:
resolved "https://registry.yarnpkg.com/ws/-/ws-7.5.6.tgz#e59fc509fb15ddfb65487ee9765c5a51dec5fe7b"
integrity sha512-6GLgCqo2cy2A2rjCNFlxQS6ZljG/coZfZXclldI8FB/1G3CCI36Zd8xy2HrFVACi8tfk5XrgLQEk+P0Tnz9UcA==
ws@^8.2.3:
version "8.2.3"
resolved "https://registry.yarnpkg.com/ws/-/ws-8.2.3.tgz#63a56456db1b04367d0b721a0b80cae6d8becbba"
integrity sha512-wBuoj1BDpC6ZQ1B7DWQBYVLphPWkm8i9Y0/3YdHjHKHiohOJ1ws+3OccDWtH+PoC9DZD5WOTrJvNbWvjS6JWaA==
ws@^8.5.0:
version "8.5.0"
resolved "https://registry.yarnpkg.com/ws/-/ws-8.5.0.tgz#bfb4be96600757fe5382de12c670dab984a1ed4f"
integrity sha512-BWX0SWVgLPzYwF8lTzEy1egjhS4S4OEAHfsO8o65WOVsrnSRGaSiUaa9e0ggGlkMTtBlmOpEXiie9RUcBO86qg==
ws@~7.4.2:
version "7.4.6"
@@ -15614,12 +15630,12 @@ yeast@0.1.2:
resolved "https://registry.yarnpkg.com/yeast/-/yeast-0.1.2.tgz#008e06d8094320c372dbc2f8ed76a0ca6c8ac419"
integrity sha1-AI4G2AlDIMNy28L47XagymyKxBk=
yjs@^13.5.0, yjs@^13.5.12:
version "13.5.12"
resolved "https://registry.yarnpkg.com/yjs/-/yjs-13.5.12.tgz#7a0cf3119fb368c07243825e989a55de164b3f9c"
integrity sha512-/buy1kh8Ls+t733Lgov9hiNxCsjHSCymTuZNahj2hsPNoGbvnSdDmCz9Z4F19Yr1eUAAXQLJF3q7fiBcvPC6Qg==
yjs@^13.5.29, yjs@^13.5.34:
version "13.5.34"
resolved "https://registry.yarnpkg.com/yjs/-/yjs-13.5.34.tgz#ad9ddb8b6c0806e15b289ff0eabc4f06ba238952"
integrity sha512-w/XTk5vhCzbyd6uKKJWE6rPUBf9+heOTzgq8DBkcVgBMv7oeJVFQw2sRqY0YvuLZxURd/XVD2dcNnw8qeFH7Tw==
dependencies:
lib0 "^0.2.41"
lib0 "^0.2.49"
ylru@^1.2.0:
version "1.2.1"