y-durableobjects
Version:
[](https://gyazo.com/e94637740dbb11fc5107b0cd0850326d)
360 lines (323 loc) • 12 kB
JavaScript
"use strict";Object.defineProperty(exports, "__esModule", {value: true}); function _nullishCoalesce(lhs, rhsFn) { if (lhs != null) { return lhs; } else { return rhsFn(); } } async function _asyncNullishCoalesce(lhs, rhsFn) { if (lhs != null) { return lhs; } else { return await rhsFn(); } } function _optionalChain(ops) { let lastAccessLHS = undefined; let value = ops[0]; let i = 1; while (i < ops.length) { const op = ops[i]; const fn = ops[i + 1]; i += 2; if ((op === 'optionalAccess' || op === 'optionalCall') && value == null) { return undefined; } if (op === 'access' || op === 'optionalAccess') { lastAccessLHS = value; value = fn(value); } else if (op === 'call' || op === 'optionalCall') { value = fn((...args) => value.call(lastAccessLHS, ...args)); lastAccessLHS = undefined; } } return value; } var _class; var _class2;
var _chunkGLWCU3YDcjs = require('./chunk-GLWCU3YD.cjs');
// src/index.ts
var _hono = require('hono');
var _client = require('hono/client');
// src/yjs/index.ts
var _cloudflareworkers = require('cloudflare:workers');
var _awareness = require('y-protocols/awareness');
var _yjs = require('yjs');
// src/yjs/remote/ws-shared-doc.ts
var _decoding = require('lib0/decoding');
var _encoding = require('lib0/encoding');
var _sync = require('y-protocols/sync');
// src/yjs/message-type/index.ts
var messageType = {
sync: 0,
awareness: 1
};
var isMessageType = (type) => {
return Object.keys(messageType).includes(type);
};
var createTypedEncoder = (type) => {
if (!isMessageType(type)) {
throw new Error(`Unsupported message type: ${type}`);
}
const encoder = _encoding.createEncoder.call(void 0, );
_encoding.writeVarUint.call(void 0, encoder, messageType[type]);
return encoder;
};
// src/yjs/remote/ws-shared-doc.ts
var WSSharedDoc = (_class = class extends _yjs.Doc {
__init() {this.listeners = /* @__PURE__ */ new Set()}
__init2() {this.awareness = new (0, _awareness.Awareness)(this)}
constructor(gc = true) {
super({ gc });_class.prototype.__init.call(this);_class.prototype.__init2.call(this);;
this.awareness.setLocalState(null);
this.awareness.on("update", (changes) => {
this.awarenessChangeHandler(changes);
});
this.on("update", (update) => {
this.syncMessageHandler(update);
});
}
update(message) {
const encoder = _encoding.createEncoder.call(void 0, );
const decoder = _decoding.createDecoder.call(void 0, message);
const type = _decoding.readVarUint.call(void 0, decoder);
switch (type) {
case messageType.sync: {
_encoding.writeVarUint.call(void 0, encoder, messageType.sync);
_sync.readSyncMessage.call(void 0, decoder, encoder, this, null);
if (_encoding.length.call(void 0, encoder) > 1) {
this._notify(_encoding.toUint8Array.call(void 0, encoder));
}
break;
}
case messageType.awareness: {
_awareness.applyAwarenessUpdate.call(void 0, this.awareness, _decoding.readVarUint8Array.call(void 0, decoder), null);
break;
}
}
}
notify(listener) {
this.listeners.add(listener);
return () => {
this.listeners.delete(listener);
};
}
syncMessageHandler(update) {
const encoder = createTypedEncoder("sync");
_sync.writeUpdate.call(void 0, encoder, update);
this._notify(_encoding.toUint8Array.call(void 0, encoder));
}
awarenessChangeHandler({
added,
updated,
removed
}) {
const changed = [...added, ...updated, ...removed];
const encoder = createTypedEncoder("awareness");
const update = _awareness.encodeAwarenessUpdate.call(void 0,
this.awareness,
changed,
this.awareness.states
);
_encoding.writeVarUint8Array.call(void 0, encoder, update);
this._notify(_encoding.toUint8Array.call(void 0, encoder));
}
_notify(message) {
for (const subscriber of this.listeners) {
subscriber(message);
}
}
}, _class);
// src/yjs/client/setup.ts
var setupWSConnection = (ws, doc) => {
{
const encoder = createTypedEncoder("sync");
_sync.writeSyncStep1.call(void 0, encoder, doc);
ws.send(_encoding.toUint8Array.call(void 0, encoder));
}
{
const states = doc.awareness.getStates();
if (states.size > 0) {
const encoder = createTypedEncoder("awareness");
const update = _awareness.encodeAwarenessUpdate.call(void 0,
doc.awareness,
Array.from(states.keys())
);
_encoding.writeVarUint8Array.call(void 0, encoder, update);
ws.send(_encoding.toUint8Array.call(void 0, encoder));
}
}
};
// src/yjs/hono/index.ts
var createApp = (service) => {
const app2 = new (0, _hono.Hono)();
return app2.get("/rooms/:roomId", async (c) => {
const roomId = c.req.param("roomId");
const client = await service.createRoom(roomId);
return new Response(null, {
webSocket: client,
status: 101,
statusText: "Switching Protocols"
});
});
};
// src/yjs/storage/index.ts
// src/yjs/storage/storage-key/index.ts
var storageKey = (key) => {
return `ydoc:${key.type}:${_nullishCoalesce(key.name, () => ( ""))}`;
};
// src/yjs/storage/index.ts
var YTransactionStorageImpl = class {
constructor(storage, options) {
this.storage = storage;
this.MAX_BYTES = _nullishCoalesce(_optionalChain([options, 'optionalAccess', _ => _.maxBytes]), () => ( 10 * 1024));
if (this.MAX_BYTES > 128 * 1024) {
throw new Error("maxBytes must be less than 128KB");
}
this.MAX_UPDATES = _nullishCoalesce(_optionalChain([options, 'optionalAccess', _2 => _2.maxUpdates]), () => ( 500));
}
async getYDoc() {
const snapshot = await this.storage.get(
storageKey({ type: "state", name: "doc" })
);
const data = await this.storage.list({
prefix: storageKey({ type: "update" })
});
const updates = Array.from(data.values());
const doc = new (0, _yjs.Doc)();
doc.transact(() => {
if (snapshot) {
_yjs.applyUpdate.call(void 0, doc, snapshot);
}
for (const update of updates) {
_yjs.applyUpdate.call(void 0, doc, update);
}
});
return doc;
}
storeUpdate(update) {
return this.storage.transaction(async (tx) => {
const bytes = await _asyncNullishCoalesce(await tx.get(storageKey({ type: "state", name: "bytes" })), async () => ( 0));
const count = await _asyncNullishCoalesce(await tx.get(storageKey({ type: "state", name: "count" })), async () => ( 0));
const updateBytes = bytes + update.byteLength;
const updateCount = count + 1;
if (updateBytes > this.MAX_BYTES || updateCount > this.MAX_UPDATES) {
const doc = await this.getYDoc();
_yjs.applyUpdate.call(void 0, doc, update);
await this._commit(doc, tx);
} else {
await tx.put(storageKey({ type: "state", name: "bytes" }), updateBytes);
await tx.put(storageKey({ type: "state", name: "count" }), updateCount);
await tx.put(storageKey({ type: "update", name: updateCount }), update);
}
});
}
async _commit(doc, tx) {
const data = await tx.list({
prefix: storageKey({ type: "update" })
});
for (const update2 of data.values()) {
_yjs.applyUpdate.call(void 0, doc, update2);
}
const update = _yjs.encodeStateAsUpdate.call(void 0, doc);
await tx.delete(Array.from(data.keys()));
await tx.put(storageKey({ type: "state", name: "bytes" }), 0);
await tx.put(storageKey({ type: "state", name: "count" }), 0);
await tx.put(storageKey({ type: "state", name: "doc" }), update);
}
async commit() {
const doc = await this.getYDoc();
return this.storage.transaction(async (tx) => {
await this._commit(doc, tx);
});
}
};
// src/yjs/index.ts
var YDurableObjects = (_class2 = class extends _cloudflareworkers.DurableObject {
constructor(state, env) {
super(state, env);_class2.prototype.__init3.call(this);_class2.prototype.__init4.call(this);_class2.prototype.__init5.call(this);_class2.prototype.__init6.call(this);_class2.prototype.__init7.call(this);;
this.state = state;
this.env = env;
void this.state.blockConcurrencyWhile(this.onStart.bind(this));
}
__init3() {this.app = createApp({
createRoom: this.createRoom.bind(this)
})}
__init4() {this.doc = new WSSharedDoc()}
__init5() {this.storage = new YTransactionStorageImpl({
get: (key) => this.state.storage.get(key),
list: (options) => this.state.storage.list(options),
put: (key, value) => this.state.storage.put(key, value),
delete: async (key) => this.state.storage.delete(Array.isArray(key) ? key : [key]),
transaction: (closure) => this.state.storage.transaction(closure)
})}
__init6() {this.sessions = /* @__PURE__ */ new Map()}
__init7() {this.awarenessClients = /* @__PURE__ */ new Set()}
async onStart() {
const doc = await this.storage.getYDoc();
_yjs.applyUpdate.call(void 0, this.doc, _yjs.encodeStateAsUpdate.call(void 0, doc));
for (const ws of this.state.getWebSockets()) {
this.registerWebSocket(ws);
}
this.doc.on("update", async (update) => {
await this.storage.storeUpdate(update);
});
this.doc.awareness.on(
"update",
async ({ added, removed, updated }) => {
for (const client of [...added, ...updated]) {
this.awarenessClients.add(client);
}
for (const client of removed) {
this.awarenessClients.delete(client);
}
}
);
}
createRoom(roomId) {
const pair = new WebSocketPair();
const client = pair[0];
const server = pair[1];
server.serializeAttachment({
roomId,
connectedAt: /* @__PURE__ */ new Date()
});
this.state.acceptWebSocket(server);
this.registerWebSocket(server);
return client;
}
fetch(request) {
return this.app.request(request, void 0, this.env);
}
async updateYDoc(update) {
this.doc.update(update);
await this.cleanup();
}
async getYDoc() {
return _yjs.encodeStateAsUpdate.call(void 0, this.doc);
}
async webSocketMessage(ws, message) {
if (!(message instanceof ArrayBuffer)) return;
const update = new Uint8Array(message);
await this.updateYDoc(update);
}
async webSocketError(ws) {
await this.unregisterWebSocket(ws);
await this.cleanup();
}
async webSocketClose(ws) {
await this.unregisterWebSocket(ws);
await this.cleanup();
}
registerWebSocket(ws) {
setupWSConnection(ws, this.doc);
const s = this.doc.notify((message) => {
ws.send(message);
});
this.sessions.set(ws, s);
}
async unregisterWebSocket(ws) {
try {
const dispose = this.sessions.get(ws);
_optionalChain([dispose, 'optionalCall', _3 => _3()]);
this.sessions.delete(ws);
const clientIds = this.awarenessClients;
_awareness.removeAwarenessStates.call(void 0, this.doc.awareness, Array.from(clientIds), null);
} catch (e) {
console.error(e);
}
}
async cleanup() {
if (this.sessions.size < 1) {
await this.storage.commit();
}
}
}, _class2);
// src/index.ts
var app = new (0, _hono.Hono)();
var yRoute = (selector) => {
const route = app.get("/:id", _chunkGLWCU3YDcjs.upgrade.call(void 0, ), async (c) => {
const obj = selector(c.env);
const stub = obj.get(obj.idFromName(c.req.param("id")));
const url = new URL("/", c.req.url);
const client = _client.hc.call(void 0, url.toString(), {
fetch: stub.fetch.bind(stub)
});
const res = await client.rooms[":roomId"].$get(
{ param: { roomId: c.req.param("id") } },
{ init: { headers: c.req.raw.headers } }
);
return new Response(null, {
webSocket: res.webSocket,
status: res.status,
statusText: res.statusText
});
});
return route;
};
exports.WSSharedDoc = WSSharedDoc; exports.YDurableObjects = YDurableObjects; exports.yRoute = yRoute;
//# sourceMappingURL=index.cjs.map