@liveblocks/server 1.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +661 -0
- package/README.md +75 -0
- package/dist/index.cjs +3285 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +1414 -0
- package/dist/index.d.ts +1414 -0
- package/dist/index.js +3285 -0
- package/dist/index.js.map +1 -0
- package/package.json +65 -0
package/dist/index.cjs
ADDED
|
@@ -0,0 +1,3285 @@
|
|
|
1
|
+
"use strict";Object.defineProperty(exports, "__esModule", {value: true}); function _interopRequireWildcard(obj) { if (obj && obj.__esModule) { return obj; } else { var newObj = {}; if (obj != null) { for (var key in obj) { if (Object.prototype.hasOwnProperty.call(obj, key)) { newObj[key] = obj[key]; } } } newObj.default = obj; return newObj; } } function _nullishCoalesce(lhs, rhsFn) { if (lhs != null) { return lhs; } else { return 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 __defProp = Object.defineProperty;
|
|
2
|
+
var __typeError = (msg) => {
|
|
3
|
+
throw TypeError(msg);
|
|
4
|
+
};
|
|
5
|
+
var __defNormalProp = (obj, key, value) => key in obj ? __defProp(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value;
|
|
6
|
+
var __publicField = (obj, key, value) => __defNormalProp(obj, typeof key !== "symbol" ? key + "" : key, value);
|
|
7
|
+
var __accessCheck = (obj, member, msg) => member.has(obj) || __typeError("Cannot " + msg);
|
|
8
|
+
var __privateGet = (obj, member, getter) => (__accessCheck(obj, member, "read from private field"), getter ? getter.call(obj) : member.get(obj));
|
|
9
|
+
var __privateAdd = (obj, member, value) => member.has(obj) ? __typeError("Cannot add the same private member more than once") : member instanceof WeakSet ? member.add(obj) : member.set(obj, value);
|
|
10
|
+
var __privateSet = (obj, member, value, setter) => (__accessCheck(obj, member, "write to private field"), setter ? setter.call(obj, value) : member.set(obj, value), value);
|
|
11
|
+
var __privateMethod = (obj, member, method) => (__accessCheck(obj, member, "access private method"), method);
|
|
12
|
+
|
|
13
|
+
// ../../node_modules/@liveblocks/core/dist/index.js
|
|
14
|
+
var __defProp2 = Object.defineProperty;
|
|
15
|
+
var __export = (target, all) => {
|
|
16
|
+
for (var name in all)
|
|
17
|
+
__defProp2(target, name, { get: all[name], enumerable: true });
|
|
18
|
+
};
|
|
19
|
+
var PKG_NAME = "@liveblocks/core";
|
|
20
|
+
var PKG_VERSION = "3.14.0-pre5";
|
|
21
|
+
var PKG_FORMAT = "esm";
|
|
22
|
+
var g = typeof globalThis !== "undefined" ? globalThis : typeof window !== "undefined" ? window : typeof global !== "undefined" ? global : {};
|
|
23
|
+
var crossLinkedDocs = "https://liveblocks.io/docs/errors/cross-linked";
|
|
24
|
+
var dupesDocs = "https://liveblocks.io/docs/errors/dupes";
|
|
25
|
+
var SPACE = " ";
|
|
26
|
+
function error(msg) {
|
|
27
|
+
if (process.env.NODE_ENV === "production") {
|
|
28
|
+
console.error(msg);
|
|
29
|
+
} else {
|
|
30
|
+
throw new Error(msg);
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
function detectDupes(pkgName, pkgVersion, pkgFormat) {
|
|
34
|
+
const pkgId = Symbol.for(pkgName);
|
|
35
|
+
const pkgBuildInfo = pkgFormat ? `${pkgVersion || "dev"} (${pkgFormat})` : pkgVersion || "dev";
|
|
36
|
+
if (!g[pkgId]) {
|
|
37
|
+
g[pkgId] = pkgBuildInfo;
|
|
38
|
+
} else if (g[pkgId] === pkgBuildInfo) {
|
|
39
|
+
} else {
|
|
40
|
+
const msg = [
|
|
41
|
+
`Multiple copies of Liveblocks are being loaded in your project. This will cause issues! See ${dupesDocs + SPACE}`,
|
|
42
|
+
"",
|
|
43
|
+
"Conflicts:",
|
|
44
|
+
`- ${pkgName} ${g[pkgId]} (already loaded)`,
|
|
45
|
+
`- ${pkgName} ${pkgBuildInfo} (trying to load this now)`
|
|
46
|
+
].join("\n");
|
|
47
|
+
error(msg);
|
|
48
|
+
}
|
|
49
|
+
if (pkgVersion && PKG_VERSION && pkgVersion !== PKG_VERSION) {
|
|
50
|
+
error(
|
|
51
|
+
[
|
|
52
|
+
`Cross-linked versions of Liveblocks found, which will cause issues! See ${crossLinkedDocs + SPACE}`,
|
|
53
|
+
"",
|
|
54
|
+
"Conflicts:",
|
|
55
|
+
`- ${PKG_NAME} is at ${PKG_VERSION}`,
|
|
56
|
+
`- ${pkgName} is at ${pkgVersion}`,
|
|
57
|
+
"",
|
|
58
|
+
"Always upgrade all Liveblocks packages to the same version number."
|
|
59
|
+
].join("\n")
|
|
60
|
+
);
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
function makeEventSource() {
|
|
64
|
+
const _observers = /* @__PURE__ */ new Set();
|
|
65
|
+
function subscribe(callback) {
|
|
66
|
+
_observers.add(callback);
|
|
67
|
+
return () => _observers.delete(callback);
|
|
68
|
+
}
|
|
69
|
+
function subscribeOnce(callback) {
|
|
70
|
+
const unsub = subscribe((event) => {
|
|
71
|
+
unsub();
|
|
72
|
+
return callback(event);
|
|
73
|
+
});
|
|
74
|
+
return unsub;
|
|
75
|
+
}
|
|
76
|
+
async function waitUntil(predicate) {
|
|
77
|
+
let unsub;
|
|
78
|
+
return new Promise((res) => {
|
|
79
|
+
unsub = subscribe((event) => {
|
|
80
|
+
if (predicate === void 0 || predicate(event)) {
|
|
81
|
+
res(event);
|
|
82
|
+
}
|
|
83
|
+
});
|
|
84
|
+
}).finally(() => _optionalChain([unsub, 'optionalCall', _2 => _2()]));
|
|
85
|
+
}
|
|
86
|
+
function notify(event) {
|
|
87
|
+
let called = false;
|
|
88
|
+
for (const callback of _observers) {
|
|
89
|
+
callback(event);
|
|
90
|
+
called = true;
|
|
91
|
+
}
|
|
92
|
+
return called;
|
|
93
|
+
}
|
|
94
|
+
function count() {
|
|
95
|
+
return _observers.size;
|
|
96
|
+
}
|
|
97
|
+
return {
|
|
98
|
+
// Private/internal control over event emission
|
|
99
|
+
notify,
|
|
100
|
+
subscribe,
|
|
101
|
+
subscribeOnce,
|
|
102
|
+
count,
|
|
103
|
+
waitUntil,
|
|
104
|
+
dispose() {
|
|
105
|
+
_observers.clear();
|
|
106
|
+
},
|
|
107
|
+
// Publicly exposable subscription API
|
|
108
|
+
observable: {
|
|
109
|
+
subscribe,
|
|
110
|
+
subscribeOnce,
|
|
111
|
+
waitUntil
|
|
112
|
+
}
|
|
113
|
+
};
|
|
114
|
+
}
|
|
115
|
+
var freeze = process.env.NODE_ENV === "production" ? (
|
|
116
|
+
/* istanbul ignore next */
|
|
117
|
+
(x) => x
|
|
118
|
+
) : Object.freeze;
|
|
119
|
+
function raise(msg) {
|
|
120
|
+
throw new Error(msg);
|
|
121
|
+
}
|
|
122
|
+
function tryParseJson(rawMessage) {
|
|
123
|
+
try {
|
|
124
|
+
return JSON.parse(rawMessage);
|
|
125
|
+
} catch (e) {
|
|
126
|
+
return void 0;
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
var kSinks = Symbol("kSinks");
|
|
130
|
+
var kTrigger = Symbol("kTrigger");
|
|
131
|
+
var signalsToTrigger = null;
|
|
132
|
+
var trackedReads = null;
|
|
133
|
+
function enqueueTrigger(signal) {
|
|
134
|
+
if (!signalsToTrigger) raise("Expected to be in an active batch");
|
|
135
|
+
signalsToTrigger.add(signal);
|
|
136
|
+
}
|
|
137
|
+
var _a, _eventSource, _b;
|
|
138
|
+
var AbstractSignal = (_b = class {
|
|
139
|
+
constructor(equals) {
|
|
140
|
+
/** @internal */
|
|
141
|
+
__publicField(this, "equals");
|
|
142
|
+
__privateAdd(this, _eventSource);
|
|
143
|
+
/** @internal */
|
|
144
|
+
__publicField(this, _a);
|
|
145
|
+
this.equals = _nullishCoalesce(equals, () => ( Object.is));
|
|
146
|
+
__privateSet(this, _eventSource, makeEventSource());
|
|
147
|
+
this[kSinks] = /* @__PURE__ */ new Set();
|
|
148
|
+
this.get = this.get.bind(this);
|
|
149
|
+
this.subscribe = this.subscribe.bind(this);
|
|
150
|
+
this.subscribeOnce = this.subscribeOnce.bind(this);
|
|
151
|
+
}
|
|
152
|
+
dispose() {
|
|
153
|
+
__privateGet(this, _eventSource).dispose();
|
|
154
|
+
__privateSet(this, _eventSource, "(disposed)");
|
|
155
|
+
this.equals = "(disposed)";
|
|
156
|
+
}
|
|
157
|
+
get hasWatchers() {
|
|
158
|
+
if (__privateGet(this, _eventSource).count() > 0) return true;
|
|
159
|
+
for (const sink of this[kSinks]) {
|
|
160
|
+
if (sink.hasWatchers) {
|
|
161
|
+
return true;
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
return false;
|
|
165
|
+
}
|
|
166
|
+
[(_a = kSinks, kTrigger)]() {
|
|
167
|
+
__privateGet(this, _eventSource).notify();
|
|
168
|
+
for (const sink of this[kSinks]) {
|
|
169
|
+
enqueueTrigger(sink);
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
subscribe(callback) {
|
|
173
|
+
if (__privateGet(this, _eventSource).count() === 0) {
|
|
174
|
+
this.get();
|
|
175
|
+
}
|
|
176
|
+
return __privateGet(this, _eventSource).subscribe(callback);
|
|
177
|
+
}
|
|
178
|
+
subscribeOnce(callback) {
|
|
179
|
+
const unsub = this.subscribe(() => {
|
|
180
|
+
unsub();
|
|
181
|
+
return callback();
|
|
182
|
+
});
|
|
183
|
+
return unsub;
|
|
184
|
+
}
|
|
185
|
+
waitUntil() {
|
|
186
|
+
throw new Error("waitUntil not supported on Signals");
|
|
187
|
+
}
|
|
188
|
+
markSinksDirty() {
|
|
189
|
+
for (const sink of this[kSinks]) {
|
|
190
|
+
sink.markDirty();
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
addSink(sink) {
|
|
194
|
+
this[kSinks].add(sink);
|
|
195
|
+
}
|
|
196
|
+
removeSink(sink) {
|
|
197
|
+
this[kSinks].delete(sink);
|
|
198
|
+
}
|
|
199
|
+
asReadonly() {
|
|
200
|
+
return this;
|
|
201
|
+
}
|
|
202
|
+
}, _eventSource = new WeakMap(), _b);
|
|
203
|
+
var INITIAL = Symbol();
|
|
204
|
+
var _prevValue, _dirty, _sources, _deps, _transform, __DerivedSignal_instances, recompute_fn, _a2;
|
|
205
|
+
var DerivedSignal = (_a2 = class extends AbstractSignal {
|
|
206
|
+
constructor(deps, transform, equals) {
|
|
207
|
+
super(equals);
|
|
208
|
+
__privateAdd(this, __DerivedSignal_instances);
|
|
209
|
+
__privateAdd(this, _prevValue);
|
|
210
|
+
__privateAdd(this, _dirty);
|
|
211
|
+
// When true, the value in #value may not be up-to-date and needs re-checking
|
|
212
|
+
__privateAdd(this, _sources);
|
|
213
|
+
__privateAdd(this, _deps);
|
|
214
|
+
__privateAdd(this, _transform);
|
|
215
|
+
__privateSet(this, _dirty, true);
|
|
216
|
+
__privateSet(this, _prevValue, INITIAL);
|
|
217
|
+
__privateSet(this, _deps, deps);
|
|
218
|
+
__privateSet(this, _sources, /* @__PURE__ */ new Set());
|
|
219
|
+
__privateSet(this, _transform, transform);
|
|
220
|
+
}
|
|
221
|
+
// prettier-ignore
|
|
222
|
+
static from(...args) {
|
|
223
|
+
const last = args.pop();
|
|
224
|
+
if (typeof last !== "function")
|
|
225
|
+
raise("Invalid .from() call, last argument expected to be a function");
|
|
226
|
+
if (typeof args[args.length - 1] === "function") {
|
|
227
|
+
const equals = last;
|
|
228
|
+
const transform = args.pop();
|
|
229
|
+
return new _a2(args, transform, equals);
|
|
230
|
+
} else {
|
|
231
|
+
const transform = last;
|
|
232
|
+
return new _a2(args, transform);
|
|
233
|
+
}
|
|
234
|
+
}
|
|
235
|
+
dispose() {
|
|
236
|
+
for (const src of __privateGet(this, _sources)) {
|
|
237
|
+
src.removeSink(this);
|
|
238
|
+
}
|
|
239
|
+
__privateSet(this, _prevValue, "(disposed)");
|
|
240
|
+
__privateSet(this, _sources, "(disposed)");
|
|
241
|
+
__privateSet(this, _deps, "(disposed)");
|
|
242
|
+
__privateSet(this, _transform, "(disposed)");
|
|
243
|
+
}
|
|
244
|
+
get isDirty() {
|
|
245
|
+
return __privateGet(this, _dirty);
|
|
246
|
+
}
|
|
247
|
+
markDirty() {
|
|
248
|
+
if (!__privateGet(this, _dirty)) {
|
|
249
|
+
__privateSet(this, _dirty, true);
|
|
250
|
+
this.markSinksDirty();
|
|
251
|
+
}
|
|
252
|
+
}
|
|
253
|
+
get() {
|
|
254
|
+
if (__privateGet(this, _dirty)) {
|
|
255
|
+
__privateMethod(this, __DerivedSignal_instances, recompute_fn).call(this);
|
|
256
|
+
}
|
|
257
|
+
_optionalChain([trackedReads, 'optionalAccess', _3 => _3.add, 'call', _4 => _4(this)]);
|
|
258
|
+
return __privateGet(this, _prevValue);
|
|
259
|
+
}
|
|
260
|
+
/**
|
|
261
|
+
* Called by the Signal system if one or more of the dependent signals have
|
|
262
|
+
* changed. In the case of a DerivedSignal, we'll only want to re-evaluate
|
|
263
|
+
* the actual value if it's being watched, or any of their sinks are being
|
|
264
|
+
* watched actively.
|
|
265
|
+
*/
|
|
266
|
+
[kTrigger]() {
|
|
267
|
+
if (!this.hasWatchers) {
|
|
268
|
+
return;
|
|
269
|
+
}
|
|
270
|
+
const updated = __privateMethod(this, __DerivedSignal_instances, recompute_fn).call(this);
|
|
271
|
+
if (updated) {
|
|
272
|
+
super[kTrigger]();
|
|
273
|
+
}
|
|
274
|
+
}
|
|
275
|
+
}, _prevValue = new WeakMap(), _dirty = new WeakMap(), _sources = new WeakMap(), _deps = new WeakMap(), _transform = new WeakMap(), __DerivedSignal_instances = new WeakSet(), recompute_fn = function() {
|
|
276
|
+
const oldTrackedReads = trackedReads;
|
|
277
|
+
let derived;
|
|
278
|
+
trackedReads = /* @__PURE__ */ new Set();
|
|
279
|
+
try {
|
|
280
|
+
derived = __privateGet(this, _transform).call(this, ...__privateGet(this, _deps).map((p) => p.get()));
|
|
281
|
+
} finally {
|
|
282
|
+
const oldSources = __privateGet(this, _sources);
|
|
283
|
+
__privateSet(this, _sources, /* @__PURE__ */ new Set());
|
|
284
|
+
for (const sig of trackedReads) {
|
|
285
|
+
__privateGet(this, _sources).add(sig);
|
|
286
|
+
oldSources.delete(sig);
|
|
287
|
+
}
|
|
288
|
+
for (const oldSource of oldSources) {
|
|
289
|
+
oldSource.removeSink(this);
|
|
290
|
+
}
|
|
291
|
+
for (const newSource of __privateGet(this, _sources)) {
|
|
292
|
+
newSource.addSink(this);
|
|
293
|
+
}
|
|
294
|
+
trackedReads = oldTrackedReads;
|
|
295
|
+
}
|
|
296
|
+
__privateSet(this, _dirty, false);
|
|
297
|
+
if (!this.equals(__privateGet(this, _prevValue), derived)) {
|
|
298
|
+
__privateSet(this, _prevValue, derived);
|
|
299
|
+
return true;
|
|
300
|
+
}
|
|
301
|
+
return false;
|
|
302
|
+
}, _a2);
|
|
303
|
+
function assertNever(_value, errmsg) {
|
|
304
|
+
throw new Error(errmsg);
|
|
305
|
+
}
|
|
306
|
+
function assert(condition, errmsg) {
|
|
307
|
+
if (process.env.NODE_ENV !== "production") {
|
|
308
|
+
if (!condition) {
|
|
309
|
+
const err = new Error(errmsg);
|
|
310
|
+
err.name = "Assertion failure";
|
|
311
|
+
throw err;
|
|
312
|
+
}
|
|
313
|
+
}
|
|
314
|
+
}
|
|
315
|
+
function nn(value, errmsg = "Expected value to be non-nullable") {
|
|
316
|
+
assert(value !== null && value !== void 0, errmsg);
|
|
317
|
+
return value;
|
|
318
|
+
}
|
|
319
|
+
var fancy_console_exports = {};
|
|
320
|
+
__export(fancy_console_exports, {
|
|
321
|
+
error: () => error2,
|
|
322
|
+
errorWithTitle: () => errorWithTitle,
|
|
323
|
+
warn: () => warn,
|
|
324
|
+
warnWithTitle: () => warnWithTitle
|
|
325
|
+
});
|
|
326
|
+
var badge = "background:#0e0d12;border-radius:9999px;color:#fff;padding:3px 7px;font-family:sans-serif;font-weight:600;";
|
|
327
|
+
var bold = "font-weight:600";
|
|
328
|
+
function wrap(method) {
|
|
329
|
+
return typeof window === "undefined" || process.env.NODE_ENV === "test" ? console[method] : (
|
|
330
|
+
/* istanbul ignore next */
|
|
331
|
+
(message, ...args) => console[method]("%cLiveblocks", badge, message, ...args)
|
|
332
|
+
);
|
|
333
|
+
}
|
|
334
|
+
var warn = wrap("warn");
|
|
335
|
+
var error2 = wrap("error");
|
|
336
|
+
function wrapWithTitle(method) {
|
|
337
|
+
return typeof window === "undefined" || process.env.NODE_ENV === "test" ? console[method] : (
|
|
338
|
+
/* istanbul ignore next */
|
|
339
|
+
(title, message, ...args) => console[method](
|
|
340
|
+
`%cLiveblocks%c ${title}`,
|
|
341
|
+
badge,
|
|
342
|
+
bold,
|
|
343
|
+
message,
|
|
344
|
+
...args
|
|
345
|
+
)
|
|
346
|
+
);
|
|
347
|
+
}
|
|
348
|
+
var warnWithTitle = wrapWithTitle("warn");
|
|
349
|
+
var errorWithTitle = wrapWithTitle("error");
|
|
350
|
+
var _defaultFn, _a3;
|
|
351
|
+
var DefaultMap = (_a3 = class extends Map {
|
|
352
|
+
/**
|
|
353
|
+
* If the default function is not provided to the constructor, it has to be
|
|
354
|
+
* provided in each .getOrCreate() call individually.
|
|
355
|
+
*/
|
|
356
|
+
constructor(defaultFn, entries2) {
|
|
357
|
+
super(entries2);
|
|
358
|
+
__privateAdd(this, _defaultFn);
|
|
359
|
+
__privateSet(this, _defaultFn, defaultFn);
|
|
360
|
+
}
|
|
361
|
+
/**
|
|
362
|
+
* Gets the value at the given key, or creates it.
|
|
363
|
+
*
|
|
364
|
+
* Difference from normal Map: if the key does not exist, it will be created
|
|
365
|
+
* on the fly using the factory function, and that value will get returned
|
|
366
|
+
* instead of `undefined`.
|
|
367
|
+
*/
|
|
368
|
+
getOrCreate(key, defaultFn) {
|
|
369
|
+
if (super.has(key)) {
|
|
370
|
+
return super.get(key);
|
|
371
|
+
} else {
|
|
372
|
+
const fn = _nullishCoalesce(_nullishCoalesce(defaultFn, () => ( __privateGet(this, _defaultFn))), () => ( raise("DefaultMap used without a factory function")));
|
|
373
|
+
const value = fn(key);
|
|
374
|
+
this.set(key, value);
|
|
375
|
+
return value;
|
|
376
|
+
}
|
|
377
|
+
}
|
|
378
|
+
}, _defaultFn = new WeakMap(), _a3);
|
|
379
|
+
var ServerMsgCode = Object.freeze({
|
|
380
|
+
// For Presence
|
|
381
|
+
UPDATE_PRESENCE: 100,
|
|
382
|
+
USER_JOINED: 101,
|
|
383
|
+
USER_LEFT: 102,
|
|
384
|
+
BROADCASTED_EVENT: 103,
|
|
385
|
+
ROOM_STATE: 104,
|
|
386
|
+
// For Storage
|
|
387
|
+
STORAGE_STATE_V7: 200,
|
|
388
|
+
// Only sent in V7
|
|
389
|
+
STORAGE_CHUNK: 210,
|
|
390
|
+
// Used in V8+
|
|
391
|
+
STORAGE_STREAM_END: 211,
|
|
392
|
+
// Used in V8+
|
|
393
|
+
UPDATE_STORAGE: 201,
|
|
394
|
+
// For Yjs Docs
|
|
395
|
+
UPDATE_YDOC: 300,
|
|
396
|
+
// For Comments
|
|
397
|
+
THREAD_CREATED: 400,
|
|
398
|
+
THREAD_DELETED: 407,
|
|
399
|
+
THREAD_METADATA_UPDATED: 401,
|
|
400
|
+
THREAD_UPDATED: 408,
|
|
401
|
+
COMMENT_CREATED: 402,
|
|
402
|
+
COMMENT_EDITED: 403,
|
|
403
|
+
COMMENT_DELETED: 404,
|
|
404
|
+
COMMENT_REACTION_ADDED: 405,
|
|
405
|
+
COMMENT_REACTION_REMOVED: 406,
|
|
406
|
+
COMMENT_METADATA_UPDATED: 409,
|
|
407
|
+
// Error codes
|
|
408
|
+
REJECT_STORAGE_OP: 299
|
|
409
|
+
// Sent if a mutation was not allowed on the server (i.e. due to permissions, limit exceeded, etc)
|
|
410
|
+
});
|
|
411
|
+
var WebsocketCloseCodes = /* @__PURE__ */ ((WebsocketCloseCodes2) => {
|
|
412
|
+
WebsocketCloseCodes2[WebsocketCloseCodes2["CLOSE_NORMAL"] = 1e3] = "CLOSE_NORMAL";
|
|
413
|
+
WebsocketCloseCodes2[WebsocketCloseCodes2["CLOSE_ABNORMAL"] = 1006] = "CLOSE_ABNORMAL";
|
|
414
|
+
WebsocketCloseCodes2[WebsocketCloseCodes2["UNEXPECTED_CONDITION"] = 1011] = "UNEXPECTED_CONDITION";
|
|
415
|
+
WebsocketCloseCodes2[WebsocketCloseCodes2["TRY_AGAIN_LATER"] = 1013] = "TRY_AGAIN_LATER";
|
|
416
|
+
WebsocketCloseCodes2[WebsocketCloseCodes2["INVALID_MESSAGE_FORMAT"] = 4e3] = "INVALID_MESSAGE_FORMAT";
|
|
417
|
+
WebsocketCloseCodes2[WebsocketCloseCodes2["NOT_ALLOWED"] = 4001] = "NOT_ALLOWED";
|
|
418
|
+
WebsocketCloseCodes2[WebsocketCloseCodes2["MAX_NUMBER_OF_MESSAGES_PER_SECONDS"] = 4002] = "MAX_NUMBER_OF_MESSAGES_PER_SECONDS";
|
|
419
|
+
WebsocketCloseCodes2[WebsocketCloseCodes2["MAX_NUMBER_OF_CONCURRENT_CONNECTIONS"] = 4003] = "MAX_NUMBER_OF_CONCURRENT_CONNECTIONS";
|
|
420
|
+
WebsocketCloseCodes2[WebsocketCloseCodes2["MAX_NUMBER_OF_MESSAGES_PER_DAY_PER_APP"] = 4004] = "MAX_NUMBER_OF_MESSAGES_PER_DAY_PER_APP";
|
|
421
|
+
WebsocketCloseCodes2[WebsocketCloseCodes2["MAX_NUMBER_OF_CONCURRENT_CONNECTIONS_PER_ROOM"] = 4005] = "MAX_NUMBER_OF_CONCURRENT_CONNECTIONS_PER_ROOM";
|
|
422
|
+
WebsocketCloseCodes2[WebsocketCloseCodes2["ROOM_ID_UPDATED"] = 4006] = "ROOM_ID_UPDATED";
|
|
423
|
+
WebsocketCloseCodes2[WebsocketCloseCodes2["KICKED"] = 4100] = "KICKED";
|
|
424
|
+
WebsocketCloseCodes2[WebsocketCloseCodes2["TOKEN_EXPIRED"] = 4109] = "TOKEN_EXPIRED";
|
|
425
|
+
WebsocketCloseCodes2[WebsocketCloseCodes2["CLOSE_WITHOUT_RETRY"] = 4999] = "CLOSE_WITHOUT_RETRY";
|
|
426
|
+
return WebsocketCloseCodes2;
|
|
427
|
+
})(WebsocketCloseCodes || {});
|
|
428
|
+
var BACKOFF_DELAYS = [250, 500, 1e3, 2e3, 4e3, 8e3, 1e4];
|
|
429
|
+
var RESET_DELAY = BACKOFF_DELAYS[0] - 1;
|
|
430
|
+
function log(level, message) {
|
|
431
|
+
const logger = level === 2 ? error2 : level === 1 ? warn : (
|
|
432
|
+
/* black hole */
|
|
433
|
+
() => {
|
|
434
|
+
}
|
|
435
|
+
);
|
|
436
|
+
return () => {
|
|
437
|
+
logger(message);
|
|
438
|
+
};
|
|
439
|
+
}
|
|
440
|
+
var logPermanentClose = log(
|
|
441
|
+
1,
|
|
442
|
+
"Connection to WebSocket closed permanently. Won't retry."
|
|
443
|
+
);
|
|
444
|
+
var kInternal = Symbol();
|
|
445
|
+
var EMPTY_OBJECT = Object.freeze({});
|
|
446
|
+
var NULL_KEYWORD_CHARS = Array.from(new Set("null"));
|
|
447
|
+
var TRUE_KEYWORD_CHARS = Array.from(new Set("true"));
|
|
448
|
+
var FALSE_KEYWORD_CHARS = Array.from(new Set("false"));
|
|
449
|
+
var ALL_KEYWORD_CHARS = Array.from(new Set("nulltruefalse"));
|
|
450
|
+
var kWILDCARD = Symbol("*");
|
|
451
|
+
var eventSource = makeEventSource();
|
|
452
|
+
if (process.env.NODE_ENV !== "production" && typeof window !== "undefined") {
|
|
453
|
+
window.addEventListener("message", (event) => {
|
|
454
|
+
if (event.source === window && _optionalChain([event, 'access', _5 => _5.data, 'optionalAccess', _6 => _6.source]) === "liveblocks-devtools-panel") {
|
|
455
|
+
eventSource.notify(event.data);
|
|
456
|
+
} else {
|
|
457
|
+
}
|
|
458
|
+
});
|
|
459
|
+
}
|
|
460
|
+
var onMessageFromPanel = eventSource.observable;
|
|
461
|
+
var loadedAt = Date.now();
|
|
462
|
+
var kPlain = Symbol("notification-settings-plain");
|
|
463
|
+
var MIN_CODE = 32;
|
|
464
|
+
var MAX_CODE = 126;
|
|
465
|
+
var NUM_DIGITS = MAX_CODE - MIN_CODE + 1;
|
|
466
|
+
var ZERO = nthDigit(0);
|
|
467
|
+
var ONE = nthDigit(1);
|
|
468
|
+
var ZERO_NINE = ZERO + nthDigit(-1);
|
|
469
|
+
function nthDigit(n) {
|
|
470
|
+
const code = MIN_CODE + (n < 0 ? NUM_DIGITS + n : n);
|
|
471
|
+
if (code < MIN_CODE || code > MAX_CODE) {
|
|
472
|
+
throw new Error(`Invalid n value: ${n}`);
|
|
473
|
+
}
|
|
474
|
+
return String.fromCharCode(code);
|
|
475
|
+
}
|
|
476
|
+
function makePosition(x, y) {
|
|
477
|
+
if (x !== void 0 && y !== void 0) {
|
|
478
|
+
return between(x, y);
|
|
479
|
+
} else if (x !== void 0) {
|
|
480
|
+
return after(x);
|
|
481
|
+
} else if (y !== void 0) {
|
|
482
|
+
return before(y);
|
|
483
|
+
} else {
|
|
484
|
+
return ONE;
|
|
485
|
+
}
|
|
486
|
+
}
|
|
487
|
+
function before(pos) {
|
|
488
|
+
const lastIndex = pos.length - 1;
|
|
489
|
+
for (let i = 0; i <= lastIndex; i++) {
|
|
490
|
+
const code = pos.charCodeAt(i);
|
|
491
|
+
if (code <= MIN_CODE) {
|
|
492
|
+
continue;
|
|
493
|
+
}
|
|
494
|
+
if (i === lastIndex) {
|
|
495
|
+
if (code === MIN_CODE + 1) {
|
|
496
|
+
return pos.substring(0, i) + ZERO_NINE;
|
|
497
|
+
} else {
|
|
498
|
+
return pos.substring(0, i) + String.fromCharCode(code - 1);
|
|
499
|
+
}
|
|
500
|
+
} else {
|
|
501
|
+
return pos.substring(0, i + 1);
|
|
502
|
+
}
|
|
503
|
+
}
|
|
504
|
+
return ONE;
|
|
505
|
+
}
|
|
506
|
+
var VIEWPORT_START = 2;
|
|
507
|
+
var VIEWPORT_STEP = 3;
|
|
508
|
+
function after(pos) {
|
|
509
|
+
for (let i = 0; i < pos.length; i++) {
|
|
510
|
+
const code = pos.charCodeAt(i);
|
|
511
|
+
if (code < MIN_CODE || code > MAX_CODE) {
|
|
512
|
+
return pos + ONE;
|
|
513
|
+
}
|
|
514
|
+
}
|
|
515
|
+
while (pos.length > 1 && pos.charCodeAt(pos.length - 1) === MIN_CODE) {
|
|
516
|
+
pos = pos.slice(0, -1);
|
|
517
|
+
}
|
|
518
|
+
if (pos.length === 0 || pos === ZERO) {
|
|
519
|
+
return ONE;
|
|
520
|
+
}
|
|
521
|
+
let viewport = VIEWPORT_START;
|
|
522
|
+
if (pos.length > VIEWPORT_START) {
|
|
523
|
+
viewport = VIEWPORT_START + Math.ceil((pos.length - VIEWPORT_START) / VIEWPORT_STEP) * VIEWPORT_STEP;
|
|
524
|
+
}
|
|
525
|
+
const result = incrementWithinViewport(pos, viewport);
|
|
526
|
+
if (result !== null) {
|
|
527
|
+
return result;
|
|
528
|
+
}
|
|
529
|
+
viewport += VIEWPORT_STEP;
|
|
530
|
+
const extendedResult = incrementWithinViewport(pos, viewport);
|
|
531
|
+
if (extendedResult !== null) {
|
|
532
|
+
return extendedResult;
|
|
533
|
+
}
|
|
534
|
+
return pos + ONE;
|
|
535
|
+
}
|
|
536
|
+
function incrementWithinViewport(pos, viewport) {
|
|
537
|
+
const digits = [];
|
|
538
|
+
for (let i = 0; i < viewport; i++) {
|
|
539
|
+
if (i < pos.length) {
|
|
540
|
+
digits.push(pos.charCodeAt(i) - MIN_CODE);
|
|
541
|
+
} else {
|
|
542
|
+
digits.push(0);
|
|
543
|
+
}
|
|
544
|
+
}
|
|
545
|
+
let carry = 1;
|
|
546
|
+
for (let i = viewport - 1; i >= 0 && carry; i--) {
|
|
547
|
+
const sum = digits[i] + carry;
|
|
548
|
+
if (sum >= NUM_DIGITS) {
|
|
549
|
+
digits[i] = 0;
|
|
550
|
+
carry = 1;
|
|
551
|
+
} else {
|
|
552
|
+
digits[i] = sum;
|
|
553
|
+
carry = 0;
|
|
554
|
+
}
|
|
555
|
+
}
|
|
556
|
+
if (carry) {
|
|
557
|
+
return null;
|
|
558
|
+
}
|
|
559
|
+
let result = "";
|
|
560
|
+
for (const d of digits) {
|
|
561
|
+
result += String.fromCharCode(d + MIN_CODE);
|
|
562
|
+
}
|
|
563
|
+
while (result.length > 1 && result.charCodeAt(result.length - 1) === MIN_CODE) {
|
|
564
|
+
result = result.slice(0, -1);
|
|
565
|
+
}
|
|
566
|
+
return result;
|
|
567
|
+
}
|
|
568
|
+
function between(lo, hi) {
|
|
569
|
+
if (lo < hi) {
|
|
570
|
+
return _between(lo, hi);
|
|
571
|
+
} else if (lo > hi) {
|
|
572
|
+
return _between(hi, lo);
|
|
573
|
+
} else {
|
|
574
|
+
throw new Error("Cannot compute value between two equal positions");
|
|
575
|
+
}
|
|
576
|
+
}
|
|
577
|
+
function _between(lo, hi) {
|
|
578
|
+
let index = 0;
|
|
579
|
+
const loLen = lo.length;
|
|
580
|
+
const hiLen = hi.length;
|
|
581
|
+
while (true) {
|
|
582
|
+
const loCode = index < loLen ? lo.charCodeAt(index) : MIN_CODE;
|
|
583
|
+
const hiCode = index < hiLen ? hi.charCodeAt(index) : MAX_CODE;
|
|
584
|
+
if (loCode === hiCode) {
|
|
585
|
+
index++;
|
|
586
|
+
continue;
|
|
587
|
+
}
|
|
588
|
+
if (hiCode - loCode === 1) {
|
|
589
|
+
const size = index + 1;
|
|
590
|
+
let prefix = lo.substring(0, size);
|
|
591
|
+
if (prefix.length < size) {
|
|
592
|
+
prefix += ZERO.repeat(size - prefix.length);
|
|
593
|
+
}
|
|
594
|
+
const suffix = lo.substring(size);
|
|
595
|
+
const nines = "";
|
|
596
|
+
return prefix + _between(suffix, nines);
|
|
597
|
+
} else {
|
|
598
|
+
return takeN(lo, index) + String.fromCharCode(hiCode + loCode >> 1);
|
|
599
|
+
}
|
|
600
|
+
}
|
|
601
|
+
}
|
|
602
|
+
function takeN(pos, n) {
|
|
603
|
+
return n < pos.length ? pos.substring(0, n) : pos + ZERO.repeat(n - pos.length);
|
|
604
|
+
}
|
|
605
|
+
var MIN_NON_ZERO_CODE = MIN_CODE + 1;
|
|
606
|
+
function isPos(str) {
|
|
607
|
+
if (str === "") {
|
|
608
|
+
return false;
|
|
609
|
+
}
|
|
610
|
+
const lastIdx = str.length - 1;
|
|
611
|
+
const last = str.charCodeAt(lastIdx);
|
|
612
|
+
if (last < MIN_NON_ZERO_CODE || last > MAX_CODE) {
|
|
613
|
+
return false;
|
|
614
|
+
}
|
|
615
|
+
for (let i = 0; i < lastIdx; i++) {
|
|
616
|
+
const code = str.charCodeAt(i);
|
|
617
|
+
if (code < MIN_CODE || code > MAX_CODE) {
|
|
618
|
+
return false;
|
|
619
|
+
}
|
|
620
|
+
}
|
|
621
|
+
return true;
|
|
622
|
+
}
|
|
623
|
+
function convertToPos(str) {
|
|
624
|
+
const codes = [];
|
|
625
|
+
for (let i = 0; i < str.length; i++) {
|
|
626
|
+
const code = str.charCodeAt(i);
|
|
627
|
+
codes.push(code < MIN_CODE ? MIN_CODE : code > MAX_CODE ? MAX_CODE : code);
|
|
628
|
+
}
|
|
629
|
+
while (codes.length > 0 && codes[codes.length - 1] === MIN_CODE) {
|
|
630
|
+
codes.length--;
|
|
631
|
+
}
|
|
632
|
+
return codes.length > 0 ? String.fromCharCode(...codes) : (
|
|
633
|
+
// Edge case: the str was a 0-only string, which is invalid. Default back to .1
|
|
634
|
+
ONE
|
|
635
|
+
);
|
|
636
|
+
}
|
|
637
|
+
function asPos(str) {
|
|
638
|
+
return isPos(str) ? str : convertToPos(str);
|
|
639
|
+
}
|
|
640
|
+
var OpCode = Object.freeze({
|
|
641
|
+
INIT: 0,
|
|
642
|
+
SET_PARENT_KEY: 1,
|
|
643
|
+
CREATE_LIST: 2,
|
|
644
|
+
UPDATE_OBJECT: 3,
|
|
645
|
+
CREATE_OBJECT: 4,
|
|
646
|
+
DELETE_CRDT: 5,
|
|
647
|
+
DELETE_OBJECT_KEY: 6,
|
|
648
|
+
CREATE_MAP: 7,
|
|
649
|
+
CREATE_REGISTER: 8
|
|
650
|
+
});
|
|
651
|
+
var NoParent = Object.freeze({ type: "NoParent" });
|
|
652
|
+
var CrdtType = Object.freeze({
|
|
653
|
+
OBJECT: 0,
|
|
654
|
+
LIST: 1,
|
|
655
|
+
MAP: 2,
|
|
656
|
+
REGISTER: 3
|
|
657
|
+
});
|
|
658
|
+
function isRootStorageNode(node) {
|
|
659
|
+
return node[0] === "root";
|
|
660
|
+
}
|
|
661
|
+
function isObjectStorageNode(node) {
|
|
662
|
+
return node[1].type === CrdtType.OBJECT;
|
|
663
|
+
}
|
|
664
|
+
function isListStorageNode(node) {
|
|
665
|
+
return node[1].type === CrdtType.LIST;
|
|
666
|
+
}
|
|
667
|
+
function isMapStorageNode(node) {
|
|
668
|
+
return node[1].type === CrdtType.MAP;
|
|
669
|
+
}
|
|
670
|
+
function isRegisterStorageNode(node) {
|
|
671
|
+
return node[1].type === CrdtType.REGISTER;
|
|
672
|
+
}
|
|
673
|
+
function* nodeStreamToCompactNodes(nodes) {
|
|
674
|
+
for (const node of nodes) {
|
|
675
|
+
if (isObjectStorageNode(node)) {
|
|
676
|
+
if (isRootStorageNode(node)) {
|
|
677
|
+
const id = node[0];
|
|
678
|
+
const crdt = node[1];
|
|
679
|
+
yield [id, crdt.data];
|
|
680
|
+
} else {
|
|
681
|
+
const id = node[0];
|
|
682
|
+
const crdt = node[1];
|
|
683
|
+
yield [id, CrdtType.OBJECT, crdt.parentId, crdt.parentKey, crdt.data];
|
|
684
|
+
}
|
|
685
|
+
} else if (isListStorageNode(node)) {
|
|
686
|
+
const id = node[0];
|
|
687
|
+
const crdt = node[1];
|
|
688
|
+
yield [id, CrdtType.LIST, crdt.parentId, crdt.parentKey];
|
|
689
|
+
} else if (isMapStorageNode(node)) {
|
|
690
|
+
const id = node[0];
|
|
691
|
+
const crdt = node[1];
|
|
692
|
+
yield [id, CrdtType.MAP, crdt.parentId, crdt.parentKey];
|
|
693
|
+
} else if (isRegisterStorageNode(node)) {
|
|
694
|
+
const id = node[0];
|
|
695
|
+
const crdt = node[1];
|
|
696
|
+
yield [id, CrdtType.REGISTER, crdt.parentId, crdt.parentKey, crdt.data];
|
|
697
|
+
} else {
|
|
698
|
+
}
|
|
699
|
+
}
|
|
700
|
+
}
|
|
701
|
+
var MAX_LIVE_OBJECT_SIZE = 128 * 1024;
|
|
702
|
+
function isJsonScalar(data) {
|
|
703
|
+
return data === null || typeof data === "string" || typeof data === "number" || typeof data === "boolean";
|
|
704
|
+
}
|
|
705
|
+
function isJsonArray(data) {
|
|
706
|
+
return Array.isArray(data);
|
|
707
|
+
}
|
|
708
|
+
function isJsonObject(data) {
|
|
709
|
+
return !isJsonScalar(data) && !isJsonArray(data);
|
|
710
|
+
}
|
|
711
|
+
var ClientMsgCode = Object.freeze({
|
|
712
|
+
// For Presence
|
|
713
|
+
UPDATE_PRESENCE: 100,
|
|
714
|
+
BROADCAST_EVENT: 103,
|
|
715
|
+
// For Storage
|
|
716
|
+
FETCH_STORAGE: 200,
|
|
717
|
+
UPDATE_STORAGE: 201,
|
|
718
|
+
// For Yjs support
|
|
719
|
+
FETCH_YDOC: 300,
|
|
720
|
+
UPDATE_YDOC: 301
|
|
721
|
+
});
|
|
722
|
+
var MAX_SOCKET_MESSAGE_SIZE = 1024 * 1024 - 512;
|
|
723
|
+
var htmlEscapables = {
|
|
724
|
+
"&": "&",
|
|
725
|
+
"<": "<",
|
|
726
|
+
">": ">",
|
|
727
|
+
'"': """,
|
|
728
|
+
"'": "'"
|
|
729
|
+
};
|
|
730
|
+
var htmlEscapablesRegex = new RegExp(
|
|
731
|
+
Object.keys(htmlEscapables).map((entity) => `\\${entity}`).join("|"),
|
|
732
|
+
"g"
|
|
733
|
+
);
|
|
734
|
+
var markdownEscapables = {
|
|
735
|
+
_: "\\_",
|
|
736
|
+
"*": "\\*",
|
|
737
|
+
"#": "\\#",
|
|
738
|
+
"`": "\\`",
|
|
739
|
+
"~": "\\~",
|
|
740
|
+
"!": "\\!",
|
|
741
|
+
"|": "\\|",
|
|
742
|
+
"(": "\\(",
|
|
743
|
+
")": "\\)",
|
|
744
|
+
"{": "\\{",
|
|
745
|
+
"}": "\\}",
|
|
746
|
+
"[": "\\[",
|
|
747
|
+
"]": "\\]"
|
|
748
|
+
};
|
|
749
|
+
var markdownEscapablesRegex = new RegExp(
|
|
750
|
+
Object.keys(markdownEscapables).map((entity) => `\\${entity}`).join("|"),
|
|
751
|
+
"g"
|
|
752
|
+
);
|
|
753
|
+
detectDupes(PKG_NAME, PKG_VERSION, PKG_FORMAT);
|
|
754
|
+
|
|
755
|
+
// src/decoders/ClientMsg.ts
|
|
756
|
+
|
|
757
|
+
|
|
758
|
+
|
|
759
|
+
|
|
760
|
+
|
|
761
|
+
|
|
762
|
+
|
|
763
|
+
|
|
764
|
+
|
|
765
|
+
var _decoders = require('decoders');
|
|
766
|
+
|
|
767
|
+
// src/decoders/jsonYolo.ts
|
|
768
|
+
|
|
769
|
+
var jsonYolo = _decoders.unknown;
|
|
770
|
+
var jsonObjectYolo = jsonYolo.refine(
|
|
771
|
+
(value) => value !== null && typeof value === "object" && !Array.isArray(value),
|
|
772
|
+
"Must be JSON object"
|
|
773
|
+
);
|
|
774
|
+
|
|
775
|
+
// src/decoders/Op.ts
|
|
776
|
+
|
|
777
|
+
var updateObjectOp = _decoders.object.call(void 0, {
|
|
778
|
+
type: _decoders.constant.call(void 0, OpCode.UPDATE_OBJECT),
|
|
779
|
+
opId: _decoders.string,
|
|
780
|
+
id: _decoders.string,
|
|
781
|
+
data: jsonObjectYolo
|
|
782
|
+
});
|
|
783
|
+
var createObjectOp = _decoders.object.call(void 0, {
|
|
784
|
+
type: _decoders.constant.call(void 0, OpCode.CREATE_OBJECT),
|
|
785
|
+
opId: _decoders.string,
|
|
786
|
+
id: _decoders.string,
|
|
787
|
+
parentId: _decoders.string,
|
|
788
|
+
parentKey: _decoders.string,
|
|
789
|
+
data: jsonObjectYolo,
|
|
790
|
+
intent: _decoders.optional.call(void 0, _decoders.constant.call(void 0, "set")),
|
|
791
|
+
deletedId: _decoders.optional.call(void 0, _decoders.string)
|
|
792
|
+
});
|
|
793
|
+
var createListOp = _decoders.object.call(void 0, {
|
|
794
|
+
type: _decoders.constant.call(void 0, OpCode.CREATE_LIST),
|
|
795
|
+
opId: _decoders.string,
|
|
796
|
+
id: _decoders.string,
|
|
797
|
+
parentId: _decoders.string,
|
|
798
|
+
parentKey: _decoders.string,
|
|
799
|
+
intent: _decoders.optional.call(void 0, _decoders.constant.call(void 0, "set")),
|
|
800
|
+
deletedId: _decoders.optional.call(void 0, _decoders.string)
|
|
801
|
+
});
|
|
802
|
+
var createMapOp = _decoders.object.call(void 0, {
|
|
803
|
+
type: _decoders.constant.call(void 0, OpCode.CREATE_MAP),
|
|
804
|
+
opId: _decoders.string,
|
|
805
|
+
id: _decoders.string,
|
|
806
|
+
parentId: _decoders.string,
|
|
807
|
+
parentKey: _decoders.string,
|
|
808
|
+
intent: _decoders.optional.call(void 0, _decoders.constant.call(void 0, "set")),
|
|
809
|
+
deletedId: _decoders.optional.call(void 0, _decoders.string)
|
|
810
|
+
});
|
|
811
|
+
var createRegisterOp = _decoders.object.call(void 0, {
|
|
812
|
+
type: _decoders.constant.call(void 0, OpCode.CREATE_REGISTER),
|
|
813
|
+
opId: _decoders.string,
|
|
814
|
+
id: _decoders.string,
|
|
815
|
+
parentId: _decoders.string,
|
|
816
|
+
parentKey: _decoders.string,
|
|
817
|
+
data: jsonYolo,
|
|
818
|
+
intent: _decoders.optional.call(void 0, _decoders.constant.call(void 0, "set")),
|
|
819
|
+
deletedId: _decoders.optional.call(void 0, _decoders.string)
|
|
820
|
+
});
|
|
821
|
+
var deleteCrdtOp = _decoders.object.call(void 0, {
|
|
822
|
+
type: _decoders.constant.call(void 0, OpCode.DELETE_CRDT),
|
|
823
|
+
opId: _decoders.string,
|
|
824
|
+
id: _decoders.string
|
|
825
|
+
});
|
|
826
|
+
var setParentKeyOp = _decoders.object.call(void 0, {
|
|
827
|
+
type: _decoders.constant.call(void 0, OpCode.SET_PARENT_KEY),
|
|
828
|
+
opId: _decoders.string,
|
|
829
|
+
id: _decoders.string,
|
|
830
|
+
parentKey: _decoders.string
|
|
831
|
+
});
|
|
832
|
+
var deleteObjectKeyOp = _decoders.object.call(void 0, {
|
|
833
|
+
type: _decoders.constant.call(void 0, OpCode.DELETE_OBJECT_KEY),
|
|
834
|
+
opId: _decoders.string,
|
|
835
|
+
id: _decoders.string,
|
|
836
|
+
key: _decoders.string
|
|
837
|
+
});
|
|
838
|
+
var op = _decoders.taggedUnion.call(void 0, "type", {
|
|
839
|
+
[OpCode.UPDATE_OBJECT]: updateObjectOp,
|
|
840
|
+
[OpCode.CREATE_OBJECT]: createObjectOp,
|
|
841
|
+
[OpCode.CREATE_LIST]: createListOp,
|
|
842
|
+
[OpCode.CREATE_MAP]: createMapOp,
|
|
843
|
+
[OpCode.CREATE_REGISTER]: createRegisterOp,
|
|
844
|
+
[OpCode.DELETE_CRDT]: deleteCrdtOp,
|
|
845
|
+
[OpCode.SET_PARENT_KEY]: setParentKeyOp,
|
|
846
|
+
[OpCode.DELETE_OBJECT_KEY]: deleteObjectKeyOp
|
|
847
|
+
});
|
|
848
|
+
|
|
849
|
+
// src/decoders/y-types.ts
|
|
850
|
+
|
|
851
|
+
var guidDecoder = _decoders.uuid.refineType();
|
|
852
|
+
var ROOT_YDOC_ID = "root";
|
|
853
|
+
|
|
854
|
+
// src/decoders/ClientMsg.ts
|
|
855
|
+
var updatePresenceClientMsg = _decoders.object.call(void 0, {
|
|
856
|
+
type: _decoders.constant.call(void 0, ClientMsgCode.UPDATE_PRESENCE),
|
|
857
|
+
data: jsonObjectYolo,
|
|
858
|
+
targetActor: _decoders.optional.call(void 0, _decoders.number)
|
|
859
|
+
});
|
|
860
|
+
var broadcastEventClientMsg = _decoders.object.call(void 0, {
|
|
861
|
+
type: _decoders.constant.call(void 0, ClientMsgCode.BROADCAST_EVENT),
|
|
862
|
+
event: jsonYolo
|
|
863
|
+
});
|
|
864
|
+
var fetchStorageClientMsg = _decoders.object.call(void 0, {
|
|
865
|
+
type: _decoders.constant.call(void 0, ClientMsgCode.FETCH_STORAGE)
|
|
866
|
+
});
|
|
867
|
+
var updateStorageClientMsg = _decoders.object.call(void 0, {
|
|
868
|
+
type: _decoders.constant.call(void 0, ClientMsgCode.UPDATE_STORAGE),
|
|
869
|
+
ops: _decoders.array.call(void 0, op)
|
|
870
|
+
});
|
|
871
|
+
var fetchYDocClientMsg = _decoders.object.call(void 0, {
|
|
872
|
+
type: _decoders.constant.call(void 0, ClientMsgCode.FETCH_YDOC),
|
|
873
|
+
vector: _decoders.string.refineType(),
|
|
874
|
+
guid: _decoders.optional.call(void 0, guidDecoder),
|
|
875
|
+
// Don't specify to update the root doc
|
|
876
|
+
v2: _decoders.optional.call(void 0, _decoders.boolean)
|
|
877
|
+
});
|
|
878
|
+
var updateYDocClientMsg = _decoders.object.call(void 0, {
|
|
879
|
+
type: _decoders.constant.call(void 0, ClientMsgCode.UPDATE_YDOC),
|
|
880
|
+
update: _decoders.string.refineType(),
|
|
881
|
+
guid: _decoders.optional.call(void 0, guidDecoder),
|
|
882
|
+
// Don't specify to update the root doc
|
|
883
|
+
v2: _decoders.optional.call(void 0, _decoders.boolean)
|
|
884
|
+
});
|
|
885
|
+
var clientMsgDecoder = _decoders.taggedUnion.call(void 0, "type", {
|
|
886
|
+
[ClientMsgCode.UPDATE_PRESENCE]: updatePresenceClientMsg,
|
|
887
|
+
[ClientMsgCode.BROADCAST_EVENT]: broadcastEventClientMsg,
|
|
888
|
+
[ClientMsgCode.FETCH_STORAGE]: fetchStorageClientMsg,
|
|
889
|
+
[ClientMsgCode.UPDATE_STORAGE]: updateStorageClientMsg,
|
|
890
|
+
[ClientMsgCode.FETCH_YDOC]: fetchYDocClientMsg,
|
|
891
|
+
[ClientMsgCode.UPDATE_YDOC]: updateYDocClientMsg
|
|
892
|
+
}).describe("Must be a valid client message");
|
|
893
|
+
var transientClientMsgDecoder = _decoders.taggedUnion.call(void 0, "type", {
|
|
894
|
+
// [ClientMsgCode.UPDATE_PRESENCE]: updatePresenceClientMsg,
|
|
895
|
+
// [ClientMsgCode.BROADCAST_EVENT]: broadcastEventClientMsg,
|
|
896
|
+
// [ClientMsgCode.FETCH_STORAGE]: fetchStorageClientMsg,
|
|
897
|
+
[ClientMsgCode.UPDATE_STORAGE]: updateStorageClientMsg
|
|
898
|
+
// [ClientMsgCode.FETCH_YDOC]: fetchYDocClientMsg,
|
|
899
|
+
// [ClientMsgCode.UPDATE_YDOC]: updateYDocClientMsg,
|
|
900
|
+
}).describe("Must be a valid transient client message");
|
|
901
|
+
|
|
902
|
+
// src/formats/LossyJson.ts
|
|
903
|
+
function snapshotToLossyJson_eager(snapshot2) {
|
|
904
|
+
try {
|
|
905
|
+
return buildObject(snapshot2, "root", snapshot2.get_root().data);
|
|
906
|
+
} finally {
|
|
907
|
+
snapshot2.destroy();
|
|
908
|
+
}
|
|
909
|
+
}
|
|
910
|
+
function buildNode(snapshot2, id) {
|
|
911
|
+
const node = snapshot2.get_node(id);
|
|
912
|
+
if (node.type === CrdtType.OBJECT) {
|
|
913
|
+
return buildObject(snapshot2, id, node.data);
|
|
914
|
+
} else if (node.type === CrdtType.LIST) {
|
|
915
|
+
return buildList(snapshot2, id);
|
|
916
|
+
} else if (node.type === CrdtType.MAP) {
|
|
917
|
+
return buildMap(snapshot2, id);
|
|
918
|
+
} else {
|
|
919
|
+
return node.data;
|
|
920
|
+
}
|
|
921
|
+
}
|
|
922
|
+
function buildObject(snapshot2, id, staticData) {
|
|
923
|
+
const data = Object.assign(/* @__PURE__ */ Object.create(null), staticData);
|
|
924
|
+
for (const [key, childId] of snapshot2.iter_children(id)) {
|
|
925
|
+
data[key] = buildNode(snapshot2, childId);
|
|
926
|
+
}
|
|
927
|
+
return data;
|
|
928
|
+
}
|
|
929
|
+
function buildList(snapshot2, id) {
|
|
930
|
+
const data = [];
|
|
931
|
+
for (const [_, childId] of snapshot2.iter_children(id)) {
|
|
932
|
+
data.push(buildNode(snapshot2, childId));
|
|
933
|
+
}
|
|
934
|
+
return data;
|
|
935
|
+
}
|
|
936
|
+
function buildMap(snapshot2, id) {
|
|
937
|
+
const data = /* @__PURE__ */ Object.create(null);
|
|
938
|
+
for (const [key, childId] of snapshot2.iter_children(id)) {
|
|
939
|
+
data[key] = buildNode(snapshot2, childId);
|
|
940
|
+
}
|
|
941
|
+
return data;
|
|
942
|
+
}
|
|
943
|
+
function* snapshotToLossyJson_lazy(snapshot2) {
|
|
944
|
+
try {
|
|
945
|
+
const staticJson = JSON.stringify(snapshot2.get_root().data).slice(1, -1);
|
|
946
|
+
yield* emitObject(snapshot2, "root", staticJson);
|
|
947
|
+
} finally {
|
|
948
|
+
snapshot2.destroy();
|
|
949
|
+
}
|
|
950
|
+
}
|
|
951
|
+
function* emit(snapshot2, id) {
|
|
952
|
+
const node = snapshot2.get_node(id);
|
|
953
|
+
if (node.type === CrdtType.OBJECT) {
|
|
954
|
+
yield* emitObject(snapshot2, id, JSON.stringify(node.data).slice(1, -1));
|
|
955
|
+
} else if (node.type === CrdtType.LIST) {
|
|
956
|
+
yield* emitList(snapshot2, id);
|
|
957
|
+
} else if (node.type === CrdtType.MAP) {
|
|
958
|
+
yield* emitMap(snapshot2, id);
|
|
959
|
+
} else if (node.type === CrdtType.REGISTER) {
|
|
960
|
+
yield JSON.stringify(node.data);
|
|
961
|
+
}
|
|
962
|
+
}
|
|
963
|
+
function* emitObject(snapshot2, id, staticJson) {
|
|
964
|
+
let comma = staticJson.length > 0;
|
|
965
|
+
yield "{";
|
|
966
|
+
yield staticJson;
|
|
967
|
+
for (const [key, childId] of snapshot2.iter_children(id)) {
|
|
968
|
+
if (comma) yield ",";
|
|
969
|
+
else comma = true;
|
|
970
|
+
yield `${JSON.stringify(key)}:`;
|
|
971
|
+
yield* emit(snapshot2, childId);
|
|
972
|
+
}
|
|
973
|
+
yield "}";
|
|
974
|
+
}
|
|
975
|
+
function* emitList(snapshot2, id) {
|
|
976
|
+
let comma = false;
|
|
977
|
+
yield "[";
|
|
978
|
+
for (const [_, childId] of snapshot2.iter_children(id)) {
|
|
979
|
+
if (comma) yield ",";
|
|
980
|
+
else comma = true;
|
|
981
|
+
yield* emit(snapshot2, childId);
|
|
982
|
+
}
|
|
983
|
+
yield "]";
|
|
984
|
+
}
|
|
985
|
+
function* emitMap(snapshot2, id) {
|
|
986
|
+
let comma = false;
|
|
987
|
+
yield "{";
|
|
988
|
+
for (const [key, childId] of snapshot2.iter_children(id)) {
|
|
989
|
+
if (comma) yield ",";
|
|
990
|
+
else comma = true;
|
|
991
|
+
yield `${JSON.stringify(key)}:`;
|
|
992
|
+
yield* emit(snapshot2, childId);
|
|
993
|
+
}
|
|
994
|
+
yield "}";
|
|
995
|
+
}
|
|
996
|
+
|
|
997
|
+
// src/formats/NodeStream.ts
|
|
998
|
+
function* snapshotToNodeStream(snapshot2) {
|
|
999
|
+
try {
|
|
1000
|
+
yield* snapshot2.iter_all();
|
|
1001
|
+
} finally {
|
|
1002
|
+
snapshot2.destroy();
|
|
1003
|
+
}
|
|
1004
|
+
}
|
|
1005
|
+
|
|
1006
|
+
// src/formats/PlainLson.ts
|
|
1007
|
+
var SERVER_INIT_OP_PREFIX = "si";
|
|
1008
|
+
function generateId(state) {
|
|
1009
|
+
return `${SERVER_INIT_OP_PREFIX}:${state.clock++}`;
|
|
1010
|
+
}
|
|
1011
|
+
function isSpecialPlainLsonValue(value) {
|
|
1012
|
+
return isJsonObject(value) && value.liveblocksType !== void 0;
|
|
1013
|
+
}
|
|
1014
|
+
function* iterJson(key, data, parent, state) {
|
|
1015
|
+
if (isSpecialPlainLsonValue(data)) {
|
|
1016
|
+
switch (data.liveblocksType) {
|
|
1017
|
+
case "LiveObject":
|
|
1018
|
+
yield* iterObjectInner(key, data.data, parent, state);
|
|
1019
|
+
return;
|
|
1020
|
+
case "LiveList":
|
|
1021
|
+
yield* iterList(key, data.data, parent, state);
|
|
1022
|
+
return;
|
|
1023
|
+
case "LiveMap":
|
|
1024
|
+
yield* iterMap(key, data.data, parent, state);
|
|
1025
|
+
return;
|
|
1026
|
+
default:
|
|
1027
|
+
assertNever(data, "Unknown `liveblocksType` field");
|
|
1028
|
+
}
|
|
1029
|
+
} else {
|
|
1030
|
+
yield [
|
|
1031
|
+
generateId(state),
|
|
1032
|
+
{
|
|
1033
|
+
type: CrdtType.REGISTER,
|
|
1034
|
+
data,
|
|
1035
|
+
parentId: parent[0],
|
|
1036
|
+
parentKey: key
|
|
1037
|
+
}
|
|
1038
|
+
];
|
|
1039
|
+
}
|
|
1040
|
+
}
|
|
1041
|
+
function* iterMap(key, map, parent, state) {
|
|
1042
|
+
const mapTuple = [
|
|
1043
|
+
generateId(state),
|
|
1044
|
+
{ type: CrdtType.MAP, parentId: parent[0], parentKey: key }
|
|
1045
|
+
];
|
|
1046
|
+
yield mapTuple;
|
|
1047
|
+
for (const [subKey, subValue] of Object.entries(map)) {
|
|
1048
|
+
yield* iterJson(subKey, subValue, mapTuple, state);
|
|
1049
|
+
}
|
|
1050
|
+
}
|
|
1051
|
+
function* iterList(key, list, parent, state) {
|
|
1052
|
+
const id = generateId(state);
|
|
1053
|
+
const crdt = {
|
|
1054
|
+
type: CrdtType.LIST,
|
|
1055
|
+
parentId: parent[0],
|
|
1056
|
+
parentKey: key
|
|
1057
|
+
};
|
|
1058
|
+
const listTuple = [id, crdt];
|
|
1059
|
+
yield listTuple;
|
|
1060
|
+
let position = makePosition();
|
|
1061
|
+
for (const subValue of list) {
|
|
1062
|
+
yield* iterJson(position, subValue, listTuple, state);
|
|
1063
|
+
position = makePosition(position);
|
|
1064
|
+
}
|
|
1065
|
+
}
|
|
1066
|
+
function* iterObjectInner(key, value, parent, state) {
|
|
1067
|
+
const data = {};
|
|
1068
|
+
const specialChildren = [];
|
|
1069
|
+
for (const [subKey, subValue] of Object.entries(value)) {
|
|
1070
|
+
if (isSpecialPlainLsonValue(subValue)) {
|
|
1071
|
+
specialChildren.push([subKey, subValue]);
|
|
1072
|
+
} else {
|
|
1073
|
+
data[subKey] = subValue;
|
|
1074
|
+
}
|
|
1075
|
+
}
|
|
1076
|
+
const objectTuple = parent !== null ? [
|
|
1077
|
+
generateId(state),
|
|
1078
|
+
{
|
|
1079
|
+
type: CrdtType.OBJECT,
|
|
1080
|
+
data,
|
|
1081
|
+
parentId: parent[0],
|
|
1082
|
+
parentKey: key
|
|
1083
|
+
}
|
|
1084
|
+
] : ["root", { type: CrdtType.OBJECT, data }];
|
|
1085
|
+
yield objectTuple;
|
|
1086
|
+
for (const [subKey, subValue] of specialChildren) {
|
|
1087
|
+
yield* iterJson(subKey, subValue, objectTuple, state);
|
|
1088
|
+
}
|
|
1089
|
+
}
|
|
1090
|
+
function* plainLsonToNodeStream(root) {
|
|
1091
|
+
const state = { clock: 1 };
|
|
1092
|
+
yield* iterObjectInner("root", root.data, null, state);
|
|
1093
|
+
}
|
|
1094
|
+
function snapshotToPlainLson_eager(snapshot2) {
|
|
1095
|
+
try {
|
|
1096
|
+
return buildObject2(snapshot2, "root", snapshot2.get_root().data);
|
|
1097
|
+
} finally {
|
|
1098
|
+
snapshot2.destroy();
|
|
1099
|
+
}
|
|
1100
|
+
}
|
|
1101
|
+
function buildNode2(snapshot2, id) {
|
|
1102
|
+
const node = snapshot2.get_node(id);
|
|
1103
|
+
if (node.type === CrdtType.OBJECT) {
|
|
1104
|
+
return buildObject2(snapshot2, id, node.data);
|
|
1105
|
+
} else if (node.type === CrdtType.LIST) {
|
|
1106
|
+
return buildList2(snapshot2, id);
|
|
1107
|
+
} else if (node.type === CrdtType.MAP) {
|
|
1108
|
+
return buildMap2(snapshot2, id);
|
|
1109
|
+
} else {
|
|
1110
|
+
return node.data;
|
|
1111
|
+
}
|
|
1112
|
+
}
|
|
1113
|
+
function buildObject2(snapshot2, id, staticData) {
|
|
1114
|
+
const data = Object.assign(
|
|
1115
|
+
/* @__PURE__ */ Object.create(null),
|
|
1116
|
+
staticData
|
|
1117
|
+
);
|
|
1118
|
+
for (const [key, childId] of snapshot2.iter_children(id)) {
|
|
1119
|
+
data[key] = buildNode2(snapshot2, childId);
|
|
1120
|
+
}
|
|
1121
|
+
return { liveblocksType: "LiveObject", data };
|
|
1122
|
+
}
|
|
1123
|
+
function buildList2(snapshot2, id) {
|
|
1124
|
+
const data = [];
|
|
1125
|
+
for (const [_, childId] of snapshot2.iter_children(id)) {
|
|
1126
|
+
data.push(buildNode2(snapshot2, childId));
|
|
1127
|
+
}
|
|
1128
|
+
return { liveblocksType: "LiveList", data };
|
|
1129
|
+
}
|
|
1130
|
+
function buildMap2(snapshot2, id) {
|
|
1131
|
+
const data = /* @__PURE__ */ Object.create(null);
|
|
1132
|
+
for (const [key, childId] of snapshot2.iter_children(id)) {
|
|
1133
|
+
data[key] = buildNode2(snapshot2, childId);
|
|
1134
|
+
}
|
|
1135
|
+
return { liveblocksType: "LiveMap", data };
|
|
1136
|
+
}
|
|
1137
|
+
function* snapshotToPlainLson_lazy(snapshot2) {
|
|
1138
|
+
try {
|
|
1139
|
+
const staticJson = JSON.stringify(snapshot2.get_root().data).slice(1, -1);
|
|
1140
|
+
yield* emitObject2(snapshot2, "root", staticJson);
|
|
1141
|
+
} finally {
|
|
1142
|
+
snapshot2.destroy();
|
|
1143
|
+
}
|
|
1144
|
+
}
|
|
1145
|
+
function* emit2(snapshot2, id) {
|
|
1146
|
+
const node = snapshot2.get_node(id);
|
|
1147
|
+
if (node.type === CrdtType.OBJECT) {
|
|
1148
|
+
yield* emitObject2(snapshot2, id, JSON.stringify(node.data).slice(1, -1));
|
|
1149
|
+
} else if (node.type === CrdtType.LIST) {
|
|
1150
|
+
yield* emitList2(snapshot2, id);
|
|
1151
|
+
} else if (node.type === CrdtType.MAP) {
|
|
1152
|
+
yield* emitMap2(snapshot2, id);
|
|
1153
|
+
} else if (node.type === CrdtType.REGISTER) {
|
|
1154
|
+
yield JSON.stringify(node.data);
|
|
1155
|
+
}
|
|
1156
|
+
}
|
|
1157
|
+
function* emitObject2(snapshot2, id, staticJson) {
|
|
1158
|
+
let comma = staticJson.length > 0;
|
|
1159
|
+
yield '{"liveblocksType":"LiveObject","data":{';
|
|
1160
|
+
yield staticJson;
|
|
1161
|
+
for (const [key, childId] of snapshot2.iter_children(id)) {
|
|
1162
|
+
if (comma) yield ",";
|
|
1163
|
+
else comma = true;
|
|
1164
|
+
yield `${JSON.stringify(key)}:`;
|
|
1165
|
+
yield* emit2(snapshot2, childId);
|
|
1166
|
+
}
|
|
1167
|
+
yield "}}";
|
|
1168
|
+
}
|
|
1169
|
+
function* emitList2(snapshot2, id) {
|
|
1170
|
+
let comma = false;
|
|
1171
|
+
yield '{"liveblocksType":"LiveList","data":[';
|
|
1172
|
+
for (const [_, childId] of snapshot2.iter_children(id)) {
|
|
1173
|
+
if (comma) yield ",";
|
|
1174
|
+
else comma = true;
|
|
1175
|
+
yield* emit2(snapshot2, childId);
|
|
1176
|
+
}
|
|
1177
|
+
yield "]}";
|
|
1178
|
+
}
|
|
1179
|
+
function* emitMap2(snapshot2, id) {
|
|
1180
|
+
let comma = false;
|
|
1181
|
+
yield '{"liveblocksType":"LiveMap","data":{';
|
|
1182
|
+
for (const [key, childId] of snapshot2.iter_children(id)) {
|
|
1183
|
+
if (comma) yield ",";
|
|
1184
|
+
else comma = true;
|
|
1185
|
+
yield `${JSON.stringify(key)}:`;
|
|
1186
|
+
yield* emit2(snapshot2, childId);
|
|
1187
|
+
}
|
|
1188
|
+
yield "}}";
|
|
1189
|
+
}
|
|
1190
|
+
|
|
1191
|
+
// src/lib/DefaultMap.ts
|
|
1192
|
+
var _defaultFn2;
|
|
1193
|
+
var DefaultMap2 = class extends Map {
|
|
1194
|
+
/**
|
|
1195
|
+
* If the default function is not provided to the constructor, it has to be
|
|
1196
|
+
* provided in each .getOrCreate() call individually.
|
|
1197
|
+
*/
|
|
1198
|
+
constructor(defaultFn, entries) {
|
|
1199
|
+
super(entries);
|
|
1200
|
+
__privateAdd(this, _defaultFn2);
|
|
1201
|
+
__privateSet(this, _defaultFn2, defaultFn);
|
|
1202
|
+
}
|
|
1203
|
+
/**
|
|
1204
|
+
* Gets the value at the given key, or creates it.
|
|
1205
|
+
*
|
|
1206
|
+
* Difference from normal Map: if the key does not exist, it will be created
|
|
1207
|
+
* on the fly using the factory function, and that value will get returned
|
|
1208
|
+
* instead of `undefined`.
|
|
1209
|
+
*/
|
|
1210
|
+
getOrCreate(key, defaultFn) {
|
|
1211
|
+
if (super.has(key)) {
|
|
1212
|
+
return super.get(key);
|
|
1213
|
+
} else {
|
|
1214
|
+
const fn = _nullishCoalesce(_nullishCoalesce(defaultFn, () => ( __privateGet(this, _defaultFn2))), () => ( raise("DefaultMap used without a factory function")));
|
|
1215
|
+
const value = fn(key);
|
|
1216
|
+
this.set(key, value);
|
|
1217
|
+
return value;
|
|
1218
|
+
}
|
|
1219
|
+
}
|
|
1220
|
+
};
|
|
1221
|
+
_defaultFn2 = new WeakMap();
|
|
1222
|
+
|
|
1223
|
+
// src/lib/NestedMap.ts
|
|
1224
|
+
function emptyIterator() {
|
|
1225
|
+
return [][Symbol.iterator]();
|
|
1226
|
+
}
|
|
1227
|
+
var _map;
|
|
1228
|
+
var NestedMap = class {
|
|
1229
|
+
constructor() {
|
|
1230
|
+
__privateAdd(this, _map);
|
|
1231
|
+
__privateSet(this, _map, new DefaultMap2(() => /* @__PURE__ */ new Map()));
|
|
1232
|
+
}
|
|
1233
|
+
get size() {
|
|
1234
|
+
let total = 0;
|
|
1235
|
+
for (const value of __privateGet(this, _map).values()) {
|
|
1236
|
+
total += value.size;
|
|
1237
|
+
}
|
|
1238
|
+
return total;
|
|
1239
|
+
}
|
|
1240
|
+
count(key1) {
|
|
1241
|
+
return _nullishCoalesce(_optionalChain([__privateGet, 'call', _7 => _7(this, _map), 'access', _8 => _8.get, 'call', _9 => _9(key1), 'optionalAccess', _10 => _10.size]), () => ( 0));
|
|
1242
|
+
}
|
|
1243
|
+
*keys() {
|
|
1244
|
+
for (const [key1, nested] of __privateGet(this, _map)) {
|
|
1245
|
+
for (const key2 of nested.keys()) {
|
|
1246
|
+
yield [key1, key2];
|
|
1247
|
+
}
|
|
1248
|
+
}
|
|
1249
|
+
}
|
|
1250
|
+
has(key1, key2) {
|
|
1251
|
+
return _nullishCoalesce(_optionalChain([__privateGet, 'call', _11 => _11(this, _map), 'access', _12 => _12.get, 'call', _13 => _13(key1), 'optionalAccess', _14 => _14.has, 'call', _15 => _15(key2)]), () => ( false));
|
|
1252
|
+
}
|
|
1253
|
+
get(key1, key2) {
|
|
1254
|
+
return _optionalChain([__privateGet, 'call', _16 => _16(this, _map), 'access', _17 => _17.get, 'call', _18 => _18(key1), 'optionalAccess', _19 => _19.get, 'call', _20 => _20(key2)]);
|
|
1255
|
+
}
|
|
1256
|
+
set(key1, key2, value) {
|
|
1257
|
+
__privateGet(this, _map).getOrCreate(key1).set(key2, value);
|
|
1258
|
+
return this;
|
|
1259
|
+
}
|
|
1260
|
+
delete(key1, key2) {
|
|
1261
|
+
if (!__privateGet(this, _map).has(key1)) {
|
|
1262
|
+
return;
|
|
1263
|
+
}
|
|
1264
|
+
const nested = __privateGet(this, _map).get(key1);
|
|
1265
|
+
nested.delete(key2);
|
|
1266
|
+
if (nested.size === 0) {
|
|
1267
|
+
__privateGet(this, _map).delete(key1);
|
|
1268
|
+
}
|
|
1269
|
+
}
|
|
1270
|
+
clear() {
|
|
1271
|
+
__privateGet(this, _map).clear();
|
|
1272
|
+
}
|
|
1273
|
+
*[Symbol.iterator]() {
|
|
1274
|
+
for (const [key1, nested] of __privateGet(this, _map)) {
|
|
1275
|
+
for (const [key2, value] of nested) {
|
|
1276
|
+
yield [key1, key2, value];
|
|
1277
|
+
}
|
|
1278
|
+
}
|
|
1279
|
+
}
|
|
1280
|
+
entriesAt(key1) {
|
|
1281
|
+
return _nullishCoalesce(_optionalChain([__privateGet, 'call', _21 => _21(this, _map), 'access', _22 => _22.get, 'call', _23 => _23(key1), 'optionalAccess', _24 => _24.entries, 'call', _25 => _25()]), () => ( emptyIterator()));
|
|
1282
|
+
}
|
|
1283
|
+
*filterAt(key1, keys) {
|
|
1284
|
+
const nested = __privateGet(this, _map).get(key1);
|
|
1285
|
+
if (nested === void 0) {
|
|
1286
|
+
return;
|
|
1287
|
+
}
|
|
1288
|
+
for (const k2 of keys) {
|
|
1289
|
+
const value = nested.get(k2);
|
|
1290
|
+
if (value !== void 0) {
|
|
1291
|
+
yield [k2, value];
|
|
1292
|
+
}
|
|
1293
|
+
}
|
|
1294
|
+
}
|
|
1295
|
+
keysAt(key1) {
|
|
1296
|
+
return _nullishCoalesce(_optionalChain([__privateGet, 'call', _26 => _26(this, _map), 'access', _27 => _27.get, 'call', _28 => _28(key1), 'optionalAccess', _29 => _29.keys, 'call', _30 => _30()]), () => ( emptyIterator()));
|
|
1297
|
+
}
|
|
1298
|
+
valuesAt(key1) {
|
|
1299
|
+
return _nullishCoalesce(_optionalChain([__privateGet, 'call', _31 => _31(this, _map), 'access', _32 => _32.get, 'call', _33 => _33(key1), 'optionalAccess', _34 => _34.values, 'call', _35 => _35()]), () => ( emptyIterator()));
|
|
1300
|
+
}
|
|
1301
|
+
deleteAll(key1) {
|
|
1302
|
+
__privateGet(this, _map).delete(key1);
|
|
1303
|
+
}
|
|
1304
|
+
};
|
|
1305
|
+
_map = new WeakMap();
|
|
1306
|
+
|
|
1307
|
+
// src/makeInMemorySnapshot.ts
|
|
1308
|
+
function makeInMemorySnapshot(values) {
|
|
1309
|
+
const map = new Map(values);
|
|
1310
|
+
if (!map.has("root")) {
|
|
1311
|
+
map.set("root", { type: CrdtType.OBJECT, data: {} });
|
|
1312
|
+
}
|
|
1313
|
+
const entries = [];
|
|
1314
|
+
const nodeStream = map;
|
|
1315
|
+
for (const node of nodeStream) {
|
|
1316
|
+
if (isRootStorageNode(node)) continue;
|
|
1317
|
+
const [id, crdt] = node;
|
|
1318
|
+
entries.push([crdt.parentId, crdt.parentKey, id]);
|
|
1319
|
+
}
|
|
1320
|
+
entries.sort(
|
|
1321
|
+
(a, b) => a[0] < b[0] ? -1 : a[0] > b[0] ? 1 : a[1] < b[1] ? -1 : a[1] > b[1] ? 1 : 0
|
|
1322
|
+
);
|
|
1323
|
+
const revMap = new NestedMap();
|
|
1324
|
+
for (const [parentId, parentKey, id] of entries) {
|
|
1325
|
+
revMap.set(parentId, parentKey, id);
|
|
1326
|
+
}
|
|
1327
|
+
function get_node(id) {
|
|
1328
|
+
return nn(map.get(id), `Node not found: ${id}`);
|
|
1329
|
+
}
|
|
1330
|
+
return {
|
|
1331
|
+
get_root: () => nn(
|
|
1332
|
+
map.get("root"),
|
|
1333
|
+
"Root not found"
|
|
1334
|
+
),
|
|
1335
|
+
get_node,
|
|
1336
|
+
iter_children: (nodeId) => revMap.entriesAt(nodeId),
|
|
1337
|
+
iter_all: () => map,
|
|
1338
|
+
destroy() {
|
|
1339
|
+
map.clear();
|
|
1340
|
+
revMap.clear();
|
|
1341
|
+
}
|
|
1342
|
+
};
|
|
1343
|
+
}
|
|
1344
|
+
|
|
1345
|
+
// src/MetadataDB.ts
|
|
1346
|
+
function makeMetadataDB(driver) {
|
|
1347
|
+
async function get(a1, a2) {
|
|
1348
|
+
if (a2 === void 0) {
|
|
1349
|
+
return await driver.get_meta(a1);
|
|
1350
|
+
} else {
|
|
1351
|
+
return a1.value(await driver.get_meta(a2));
|
|
1352
|
+
}
|
|
1353
|
+
}
|
|
1354
|
+
return {
|
|
1355
|
+
get,
|
|
1356
|
+
put: driver.put_meta.bind(driver),
|
|
1357
|
+
delete: driver.delete_meta.bind(driver)
|
|
1358
|
+
};
|
|
1359
|
+
}
|
|
1360
|
+
|
|
1361
|
+
// src/protocol/ProtocolVersion.ts
|
|
1362
|
+
|
|
1363
|
+
var ProtocolVersion = /* @__PURE__ */ ((ProtocolVersion2) => {
|
|
1364
|
+
ProtocolVersion2[ProtocolVersion2["V7"] = 7] = "V7";
|
|
1365
|
+
ProtocolVersion2[ProtocolVersion2["V8"] = 8] = "V8";
|
|
1366
|
+
return ProtocolVersion2;
|
|
1367
|
+
})(ProtocolVersion || {});
|
|
1368
|
+
var protocolVersionDecoder = _decoders.enum_.call(void 0, ProtocolVersion).describe(
|
|
1369
|
+
"Unsupported protocol version"
|
|
1370
|
+
);
|
|
1371
|
+
|
|
1372
|
+
// src/Room.ts
|
|
1373
|
+
var _asyncmutex = require('async-mutex');
|
|
1374
|
+
|
|
1375
|
+
var _itertools = require('itertools');
|
|
1376
|
+
var _nanoid = require('nanoid');
|
|
1377
|
+
|
|
1378
|
+
// src/lib/Logger.ts
|
|
1379
|
+
var LogLevel = /* @__PURE__ */ ((LogLevel2) => {
|
|
1380
|
+
LogLevel2[LogLevel2["DEBUG"] = 0] = "DEBUG";
|
|
1381
|
+
LogLevel2[LogLevel2["INFO"] = 1] = "INFO";
|
|
1382
|
+
LogLevel2[LogLevel2["WARNING"] = 2] = "WARNING";
|
|
1383
|
+
LogLevel2[LogLevel2["ERROR"] = 3] = "ERROR";
|
|
1384
|
+
return LogLevel2;
|
|
1385
|
+
})(LogLevel || {});
|
|
1386
|
+
function formatError(err) {
|
|
1387
|
+
const prefix = `${err.name}: ${err.message}`;
|
|
1388
|
+
return (_optionalChain([err, 'access', _36 => _36.stack, 'optionalAccess', _37 => _37.startsWith, 'call', _38 => _38(prefix)]) ? err.stack : `${prefix}
|
|
1389
|
+
${_nullishCoalesce(err.stack, () => ( ""))}`).trimEnd();
|
|
1390
|
+
}
|
|
1391
|
+
var _cache;
|
|
1392
|
+
var LogTarget = class {
|
|
1393
|
+
constructor(level = 1 /* INFO */) {
|
|
1394
|
+
__publicField(this, "level");
|
|
1395
|
+
__privateAdd(this, _cache, /* @__PURE__ */ new WeakMap());
|
|
1396
|
+
this.level = typeof level === "number" ? level : _nullishCoalesce(LogLevelNames[level], () => ( 1)) /* INFO */;
|
|
1397
|
+
}
|
|
1398
|
+
/** Helper for formatting a log level */
|
|
1399
|
+
formatLevel(level) {
|
|
1400
|
+
switch (level) {
|
|
1401
|
+
case 0 /* DEBUG */:
|
|
1402
|
+
return "debug";
|
|
1403
|
+
case 1 /* INFO */:
|
|
1404
|
+
return "info";
|
|
1405
|
+
case 2 /* WARNING */:
|
|
1406
|
+
return "warn";
|
|
1407
|
+
case 3 /* ERROR */:
|
|
1408
|
+
return "error";
|
|
1409
|
+
default:
|
|
1410
|
+
return raise("Invalid log level");
|
|
1411
|
+
}
|
|
1412
|
+
}
|
|
1413
|
+
/** Helper for formatting an Arg */
|
|
1414
|
+
formatArg(arg) {
|
|
1415
|
+
return typeof arg === "object" ? arg instanceof Error ? formatError(arg) : JSON.stringify(arg) : String(arg);
|
|
1416
|
+
}
|
|
1417
|
+
/**
|
|
1418
|
+
* Helper for formatting a Context. Override this in a subclass to change the
|
|
1419
|
+
* formatting.
|
|
1420
|
+
*/
|
|
1421
|
+
formatContextImpl(context) {
|
|
1422
|
+
const parts = [];
|
|
1423
|
+
for (const [k, v] of Object.entries(_nullishCoalesce(context, () => ( {})))) {
|
|
1424
|
+
if (v !== void 0) {
|
|
1425
|
+
const sv = typeof v === "object" ? JSON.stringify(v) : v;
|
|
1426
|
+
parts.push(`${k}=${sv}`);
|
|
1427
|
+
}
|
|
1428
|
+
}
|
|
1429
|
+
return parts.length > 0 ? `[${parts.join(" ")}]` : "";
|
|
1430
|
+
}
|
|
1431
|
+
/**
|
|
1432
|
+
* Helper for formatting a Context. Will only compute the string once for
|
|
1433
|
+
* every Context instance, and keep its computed string value cached for
|
|
1434
|
+
* performance.
|
|
1435
|
+
*/
|
|
1436
|
+
formatContext(context) {
|
|
1437
|
+
let formatted = __privateGet(this, _cache).get(context);
|
|
1438
|
+
if (formatted === void 0) {
|
|
1439
|
+
formatted = this.formatContextImpl(context);
|
|
1440
|
+
__privateGet(this, _cache).set(context, formatted);
|
|
1441
|
+
}
|
|
1442
|
+
return formatted;
|
|
1443
|
+
}
|
|
1444
|
+
};
|
|
1445
|
+
_cache = new WeakMap();
|
|
1446
|
+
var CONSOLE_METHOD = {
|
|
1447
|
+
[0 /* DEBUG */]: "info",
|
|
1448
|
+
[1 /* INFO */]: "info",
|
|
1449
|
+
[2 /* WARNING */]: "warn",
|
|
1450
|
+
[3 /* ERROR */]: "error"
|
|
1451
|
+
};
|
|
1452
|
+
var ConsoleTarget = class extends LogTarget {
|
|
1453
|
+
log(level, context, arg) {
|
|
1454
|
+
console[CONSOLE_METHOD[level]](
|
|
1455
|
+
this.formatArg(arg),
|
|
1456
|
+
this.formatContext(context)
|
|
1457
|
+
);
|
|
1458
|
+
}
|
|
1459
|
+
};
|
|
1460
|
+
var LogLevelNames = {
|
|
1461
|
+
debug: 0 /* DEBUG */,
|
|
1462
|
+
info: 1 /* INFO */,
|
|
1463
|
+
warning: 2 /* WARNING */,
|
|
1464
|
+
error: 3 /* ERROR */
|
|
1465
|
+
};
|
|
1466
|
+
var Logger = class _Logger {
|
|
1467
|
+
constructor(target = new ConsoleTarget(), context = {}) {
|
|
1468
|
+
__publicField(this, "debug");
|
|
1469
|
+
__publicField(this, "info");
|
|
1470
|
+
__publicField(this, "warn");
|
|
1471
|
+
__publicField(this, "error");
|
|
1472
|
+
__publicField(this, "o");
|
|
1473
|
+
__publicField(this, "_context");
|
|
1474
|
+
__publicField(this, "_targets");
|
|
1475
|
+
this._context = context;
|
|
1476
|
+
this._targets = Array.isArray(target) ? target : [target];
|
|
1477
|
+
const minLevel = Math.min(...this._targets.map((t) => t.level));
|
|
1478
|
+
const noop = () => {
|
|
1479
|
+
};
|
|
1480
|
+
const makeLogFn = (lvl) => (arg) => this._targets.forEach((target2) => {
|
|
1481
|
+
if (target2.level <= lvl) {
|
|
1482
|
+
target2.log(lvl, this._context, arg);
|
|
1483
|
+
}
|
|
1484
|
+
});
|
|
1485
|
+
this.o = {
|
|
1486
|
+
/* eslint-disable @typescript-eslint/no-unsafe-enum-comparison */
|
|
1487
|
+
debug: minLevel <= 0 /* DEBUG */ ? makeLogFn(0 /* DEBUG */) : void 0,
|
|
1488
|
+
info: minLevel <= 1 /* INFO */ ? makeLogFn(1 /* INFO */) : void 0,
|
|
1489
|
+
warn: minLevel <= 2 /* WARNING */ ? makeLogFn(2 /* WARNING */) : void 0,
|
|
1490
|
+
error: minLevel <= 3 /* ERROR */ ? makeLogFn(3 /* ERROR */) : void 0
|
|
1491
|
+
/* eslint-enable @typescript-eslint/no-unsafe-enum-comparison */
|
|
1492
|
+
};
|
|
1493
|
+
this.debug = _nullishCoalesce(this.o.debug, () => ( noop));
|
|
1494
|
+
this.info = _nullishCoalesce(this.o.info, () => ( noop));
|
|
1495
|
+
this.warn = _nullishCoalesce(this.o.warn, () => ( noop));
|
|
1496
|
+
this.error = _nullishCoalesce(this.o.error, () => ( noop));
|
|
1497
|
+
}
|
|
1498
|
+
/**
|
|
1499
|
+
* Creates a new Logger instance with the given extra context applied. All
|
|
1500
|
+
* log calls made from that new Logger will carry all current _and_ the extra
|
|
1501
|
+
* context, with the extra context taking precedence. Assign an explicit
|
|
1502
|
+
* `undefined` value to a key to "remove" it from the context.
|
|
1503
|
+
*/
|
|
1504
|
+
withContext(extra) {
|
|
1505
|
+
const combined = { ...this._context, ...extra };
|
|
1506
|
+
return new _Logger(this._targets, combined);
|
|
1507
|
+
}
|
|
1508
|
+
};
|
|
1509
|
+
|
|
1510
|
+
// src/plugins/InMemoryDriver.ts
|
|
1511
|
+
|
|
1512
|
+
|
|
1513
|
+
// src/lib/text.ts
|
|
1514
|
+
function quote(value) {
|
|
1515
|
+
return value !== void 0 ? `'${value}'` : "???";
|
|
1516
|
+
}
|
|
1517
|
+
|
|
1518
|
+
// src/plugins/InMemoryDriver.ts
|
|
1519
|
+
function buildRevNodes(nodeStream) {
|
|
1520
|
+
const result = new NestedMap();
|
|
1521
|
+
for (const node of nodeStream) {
|
|
1522
|
+
if (isRootStorageNode(node)) continue;
|
|
1523
|
+
const [id, crdt] = node;
|
|
1524
|
+
const existing = result.get(crdt.parentId, crdt.parentKey);
|
|
1525
|
+
if (existing === void 0 || id > existing) {
|
|
1526
|
+
result.set(crdt.parentId, crdt.parentKey, id);
|
|
1527
|
+
}
|
|
1528
|
+
}
|
|
1529
|
+
return result;
|
|
1530
|
+
}
|
|
1531
|
+
function buildReverseLookup(nodes) {
|
|
1532
|
+
const revNodes = buildRevNodes(nodes);
|
|
1533
|
+
const queue = ["root"];
|
|
1534
|
+
const reachableNodes = /* @__PURE__ */ new Set();
|
|
1535
|
+
while (queue.length > 0) {
|
|
1536
|
+
const nodeId = queue.pop();
|
|
1537
|
+
const node = nn(nodes.get(nodeId));
|
|
1538
|
+
if (node.type === CrdtType.OBJECT) {
|
|
1539
|
+
for (const key of revNodes.keysAt(nodeId)) {
|
|
1540
|
+
delete node.data[key];
|
|
1541
|
+
}
|
|
1542
|
+
}
|
|
1543
|
+
if (node.type !== CrdtType.REGISTER) {
|
|
1544
|
+
queue.push(...revNodes.valuesAt(nodeId));
|
|
1545
|
+
} else {
|
|
1546
|
+
const parent = nodes.get(node.parentId);
|
|
1547
|
+
if (_optionalChain([parent, 'optionalAccess', _39 => _39.type]) === CrdtType.OBJECT) {
|
|
1548
|
+
continue;
|
|
1549
|
+
}
|
|
1550
|
+
}
|
|
1551
|
+
reachableNodes.add(nodeId);
|
|
1552
|
+
}
|
|
1553
|
+
let deletedCount = 0;
|
|
1554
|
+
for (const [id] of nodes) {
|
|
1555
|
+
if (!reachableNodes.has(id)) {
|
|
1556
|
+
nodes.delete(id);
|
|
1557
|
+
deletedCount++;
|
|
1558
|
+
}
|
|
1559
|
+
}
|
|
1560
|
+
return deletedCount === 0 ? revNodes : buildRevNodes(nodes);
|
|
1561
|
+
}
|
|
1562
|
+
function hasStaticDataAt(node, key) {
|
|
1563
|
+
return node.type === CrdtType.OBJECT && Object.prototype.hasOwnProperty.call(node.data, key) && node.data[key] !== void 0;
|
|
1564
|
+
}
|
|
1565
|
+
var InMemoryDriver = class {
|
|
1566
|
+
constructor(options) {
|
|
1567
|
+
__publicField(this, "_nextActor");
|
|
1568
|
+
__publicField(this, "_nodes");
|
|
1569
|
+
__publicField(this, "_metadb");
|
|
1570
|
+
__publicField(this, "_ydb");
|
|
1571
|
+
this._nodes = /* @__PURE__ */ new Map();
|
|
1572
|
+
this._metadb = /* @__PURE__ */ new Map();
|
|
1573
|
+
this._ydb = /* @__PURE__ */ new Map();
|
|
1574
|
+
this._nextActor = _nullishCoalesce(_optionalChain([options, 'optionalAccess', _40 => _40.initialActor]), () => ( -1));
|
|
1575
|
+
for (const [key, value] of _nullishCoalesce(_optionalChain([options, 'optionalAccess', _41 => _41.initialNodes]), () => ( []))) {
|
|
1576
|
+
this._nodes.set(key, value);
|
|
1577
|
+
}
|
|
1578
|
+
}
|
|
1579
|
+
raw_iter_nodes() {
|
|
1580
|
+
return this._nodes[Symbol.iterator]();
|
|
1581
|
+
}
|
|
1582
|
+
/** Deletes all nodes and replaces them with the given document. */
|
|
1583
|
+
DANGEROUSLY_reset_nodes(doc) {
|
|
1584
|
+
this._nodes.clear();
|
|
1585
|
+
for (const [id, node] of plainLsonToNodeStream(doc)) {
|
|
1586
|
+
this._nodes.set(id, node);
|
|
1587
|
+
}
|
|
1588
|
+
}
|
|
1589
|
+
async get_meta(key) {
|
|
1590
|
+
return this._metadb.get(key);
|
|
1591
|
+
}
|
|
1592
|
+
async put_meta(key, value) {
|
|
1593
|
+
this._metadb.set(key, value);
|
|
1594
|
+
}
|
|
1595
|
+
async delete_meta(key) {
|
|
1596
|
+
this._metadb.delete(key);
|
|
1597
|
+
}
|
|
1598
|
+
next_actor() {
|
|
1599
|
+
return ++this._nextActor;
|
|
1600
|
+
}
|
|
1601
|
+
async iter_y_updates(docId) {
|
|
1602
|
+
const prefix = `${docId}@|@`;
|
|
1603
|
+
return _itertools.imap.call(void 0,
|
|
1604
|
+
_itertools.ifilter.call(void 0, this._ydb.entries(), ([k]) => k.startsWith(prefix)),
|
|
1605
|
+
([k, v]) => [k.slice(prefix.length), v]
|
|
1606
|
+
);
|
|
1607
|
+
}
|
|
1608
|
+
async write_y_updates(docId, key, data) {
|
|
1609
|
+
this._ydb.set(`${docId}@|@${key}`, data);
|
|
1610
|
+
}
|
|
1611
|
+
async delete_y_updates(docId, keys) {
|
|
1612
|
+
for (const key of keys) {
|
|
1613
|
+
this._ydb.delete(`${docId}@|@${key}`);
|
|
1614
|
+
}
|
|
1615
|
+
}
|
|
1616
|
+
/** @private Only use this in unit tests, never in production. */
|
|
1617
|
+
async DANGEROUSLY_wipe_all_y_updates() {
|
|
1618
|
+
this._ydb.clear();
|
|
1619
|
+
}
|
|
1620
|
+
// Intercept load_nodes_api to add caching layer
|
|
1621
|
+
load_nodes_api() {
|
|
1622
|
+
const nodes = this._nodes;
|
|
1623
|
+
if (!nodes.has("root")) {
|
|
1624
|
+
nodes.set("root", { type: CrdtType.OBJECT, data: {} });
|
|
1625
|
+
}
|
|
1626
|
+
const revNodes = buildReverseLookup(nodes);
|
|
1627
|
+
function get_next_sibling(parentId, pos) {
|
|
1628
|
+
let nextPos;
|
|
1629
|
+
for (const siblingKey of revNodes.keysAt(parentId)) {
|
|
1630
|
+
const siblingPos = asPos(siblingKey);
|
|
1631
|
+
if (siblingPos > pos && (nextPos === void 0 || siblingPos < nextPos)) {
|
|
1632
|
+
nextPos = siblingPos;
|
|
1633
|
+
}
|
|
1634
|
+
}
|
|
1635
|
+
return nextPos;
|
|
1636
|
+
}
|
|
1637
|
+
async function set_child(id, node, allowOverwrite = false) {
|
|
1638
|
+
const parentNode = nodes.get(node.parentId);
|
|
1639
|
+
if (parentNode === void 0) {
|
|
1640
|
+
throw new Error(`No such parent ${quote(node.parentId)}`);
|
|
1641
|
+
}
|
|
1642
|
+
if (node.type === CrdtType.REGISTER && parentNode.type === CrdtType.OBJECT) {
|
|
1643
|
+
throw new Error("Cannot add register under object");
|
|
1644
|
+
}
|
|
1645
|
+
const conflictingSiblingId = revNodes.get(node.parentId, node.parentKey);
|
|
1646
|
+
if (conflictingSiblingId !== id) {
|
|
1647
|
+
const parentNode2 = nodes.get(node.parentId);
|
|
1648
|
+
const hasConflictingData = parentNode2 !== void 0 && hasStaticDataAt(parentNode2, node.parentKey);
|
|
1649
|
+
if (conflictingSiblingId !== void 0 || hasConflictingData) {
|
|
1650
|
+
if (allowOverwrite) {
|
|
1651
|
+
delete_child_key(node.parentId, node.parentKey);
|
|
1652
|
+
} else {
|
|
1653
|
+
throw new Error(`Key ${quote(node.parentKey)} already exists`);
|
|
1654
|
+
}
|
|
1655
|
+
}
|
|
1656
|
+
revNodes.set(node.parentId, node.parentKey, id);
|
|
1657
|
+
}
|
|
1658
|
+
nodes.set(id, node);
|
|
1659
|
+
}
|
|
1660
|
+
async function move_sibling(id, newPos) {
|
|
1661
|
+
const node = nodes.get(id);
|
|
1662
|
+
if (_optionalChain([node, 'optionalAccess', _42 => _42.parentId]) === void 0) {
|
|
1663
|
+
return;
|
|
1664
|
+
}
|
|
1665
|
+
if (revNodes.has(node.parentId, newPos))
|
|
1666
|
+
throw new Error(`Pos ${quote(newPos)} already taken`);
|
|
1667
|
+
revNodes.delete(node.parentId, node.parentKey);
|
|
1668
|
+
const newNode = { ...node, parentKey: newPos };
|
|
1669
|
+
nodes.set(id, newNode);
|
|
1670
|
+
revNodes.set(node.parentId, newPos, id);
|
|
1671
|
+
}
|
|
1672
|
+
async function set_object_data(id, data, allowOverwrite = false) {
|
|
1673
|
+
const node = nodes.get(id);
|
|
1674
|
+
if (_optionalChain([node, 'optionalAccess', _43 => _43.type]) !== CrdtType.OBJECT) {
|
|
1675
|
+
return;
|
|
1676
|
+
}
|
|
1677
|
+
for (const key of Object.keys(data)) {
|
|
1678
|
+
const childId = revNodes.get(id, key);
|
|
1679
|
+
if (childId !== void 0) {
|
|
1680
|
+
if (allowOverwrite) {
|
|
1681
|
+
delete_node(childId);
|
|
1682
|
+
} else {
|
|
1683
|
+
throw new Error(`Child node already exists under ${quote(key)}`);
|
|
1684
|
+
}
|
|
1685
|
+
}
|
|
1686
|
+
}
|
|
1687
|
+
nodes.set(id, { ...node, data: { ...node.data, ...data } });
|
|
1688
|
+
}
|
|
1689
|
+
function delete_node(id) {
|
|
1690
|
+
const node = nodes.get(id);
|
|
1691
|
+
if (_optionalChain([node, 'optionalAccess', _44 => _44.parentId]) === void 0) {
|
|
1692
|
+
return;
|
|
1693
|
+
}
|
|
1694
|
+
revNodes.delete(node.parentId, node.parentKey);
|
|
1695
|
+
const queue = [id];
|
|
1696
|
+
while (queue.length > 0) {
|
|
1697
|
+
const currid = queue.pop();
|
|
1698
|
+
queue.push(...revNodes.valuesAt(currid));
|
|
1699
|
+
nodes.delete(currid);
|
|
1700
|
+
revNodes.deleteAll(currid);
|
|
1701
|
+
}
|
|
1702
|
+
}
|
|
1703
|
+
function delete_child_key(id, key) {
|
|
1704
|
+
const node = nodes.get(id);
|
|
1705
|
+
if (node !== void 0 && hasStaticDataAt(node, key)) {
|
|
1706
|
+
const { [key]: _, ...rest } = node.data;
|
|
1707
|
+
nodes.set(id, { ...node, data: rest });
|
|
1708
|
+
}
|
|
1709
|
+
const childId = revNodes.get(id, key);
|
|
1710
|
+
if (childId !== void 0) {
|
|
1711
|
+
delete_node(childId);
|
|
1712
|
+
}
|
|
1713
|
+
}
|
|
1714
|
+
const api = {
|
|
1715
|
+
/**
|
|
1716
|
+
* Return the node with the given id, or undefined if no such node exists.
|
|
1717
|
+
* Must always return a valid root node for id="root", even if empty.
|
|
1718
|
+
*/
|
|
1719
|
+
get_node: (id) => nodes.get(id),
|
|
1720
|
+
/**
|
|
1721
|
+
* Yield all nodes as [id, node] pairs. Must always include the root node.
|
|
1722
|
+
*/
|
|
1723
|
+
iter_nodes: () => nodes,
|
|
1724
|
+
/**
|
|
1725
|
+
* Return true iff a node with the given id exists. Must return true for "root".
|
|
1726
|
+
*/
|
|
1727
|
+
has_node: (id) => nodes.has(id),
|
|
1728
|
+
/**
|
|
1729
|
+
* Return the id of the child node at (parentId, parentKey), or undefined if
|
|
1730
|
+
* none. Only checks child nodes registered via set_child, NOT static data
|
|
1731
|
+
* keys on OBJECT nodes.
|
|
1732
|
+
*/
|
|
1733
|
+
get_child_at: (id, key) => revNodes.get(id, key),
|
|
1734
|
+
/**
|
|
1735
|
+
* Return true iff a child node exists at (parentId, parentKey). Static data
|
|
1736
|
+
* keys on OBJECT nodes do not count—return false for those.
|
|
1737
|
+
*/
|
|
1738
|
+
has_child_at: (id, key) => revNodes.has(id, key),
|
|
1739
|
+
/**
|
|
1740
|
+
* Return the position of the closest sibling "to the right" of `pos` under
|
|
1741
|
+
* parentId, or undefined if no such sibling exists. The given `pos` may, but
|
|
1742
|
+
* does not have to exist already. Positions compare lexicographically.
|
|
1743
|
+
*/
|
|
1744
|
+
get_next_sibling,
|
|
1745
|
+
/**
|
|
1746
|
+
* Insert a child node with the given id.
|
|
1747
|
+
*
|
|
1748
|
+
* If allowOverwrite=false (default): throw if a node with this id exists.
|
|
1749
|
+
* If allowOverwrite=true: replace any existing node at this id, deleting its
|
|
1750
|
+
* entire subtree if it has children.
|
|
1751
|
+
*/
|
|
1752
|
+
set_child,
|
|
1753
|
+
/**
|
|
1754
|
+
* Change a node's parentKey, effectively repositioning the node within its
|
|
1755
|
+
* parent. The new position must be free.
|
|
1756
|
+
* Throw if another node already occupies (parentId, newPos).
|
|
1757
|
+
*/
|
|
1758
|
+
move_sibling,
|
|
1759
|
+
/**
|
|
1760
|
+
* Delete a node and its entire subtree recursively.
|
|
1761
|
+
* Ignore if id="root" (root is immortal).
|
|
1762
|
+
*/
|
|
1763
|
+
delete_node,
|
|
1764
|
+
/**
|
|
1765
|
+
* Delete a key from node `id`. Handle two cases:
|
|
1766
|
+
*
|
|
1767
|
+
* 1. If id is an OBJECT with `key` in its data: remove that data field.
|
|
1768
|
+
* 2. If a child exists at (id, key): delete that child and all its
|
|
1769
|
+
* descendants recursively.
|
|
1770
|
+
*
|
|
1771
|
+
* No-op if neither applies or if the node doesn't exist.
|
|
1772
|
+
*/
|
|
1773
|
+
delete_child_key,
|
|
1774
|
+
/**
|
|
1775
|
+
* Replace the data object of an OBJECT node.
|
|
1776
|
+
*
|
|
1777
|
+
* If allowOverwrite=false (default): throw if any key in `data` conflicts
|
|
1778
|
+
* with an existing child's parentKey.
|
|
1779
|
+
* If allowOverwrite=true: first delete any conflicting children (and their
|
|
1780
|
+
* entire subtrees), then set the data.
|
|
1781
|
+
*/
|
|
1782
|
+
set_object_data,
|
|
1783
|
+
/**
|
|
1784
|
+
* Return a readable snapshot of the storage tree.
|
|
1785
|
+
*
|
|
1786
|
+
* @param lowMemory When true, the call site hints that the snapshot should
|
|
1787
|
+
* be optimized for lower memory consumption, even if that means slower
|
|
1788
|
+
* access.
|
|
1789
|
+
*/
|
|
1790
|
+
get_snapshot(_lowMemory) {
|
|
1791
|
+
return makeInMemorySnapshot(nodes);
|
|
1792
|
+
}
|
|
1793
|
+
};
|
|
1794
|
+
return api;
|
|
1795
|
+
}
|
|
1796
|
+
};
|
|
1797
|
+
function makeNewInMemoryDriver(options) {
|
|
1798
|
+
return new InMemoryDriver(options);
|
|
1799
|
+
}
|
|
1800
|
+
|
|
1801
|
+
// src/Storage.ts
|
|
1802
|
+
function accept(op2, fix) {
|
|
1803
|
+
return { action: "accepted", op: op2, fix };
|
|
1804
|
+
}
|
|
1805
|
+
function ignore(ignoredOp) {
|
|
1806
|
+
return { action: "ignored", ignoredOpId: ignoredOp.opId };
|
|
1807
|
+
}
|
|
1808
|
+
function nodeFromCreateChildOp(op2) {
|
|
1809
|
+
switch (op2.type) {
|
|
1810
|
+
case OpCode.CREATE_LIST:
|
|
1811
|
+
return {
|
|
1812
|
+
type: CrdtType.LIST,
|
|
1813
|
+
parentId: op2.parentId,
|
|
1814
|
+
parentKey: op2.parentKey
|
|
1815
|
+
};
|
|
1816
|
+
case OpCode.CREATE_MAP:
|
|
1817
|
+
return {
|
|
1818
|
+
type: CrdtType.MAP,
|
|
1819
|
+
parentId: op2.parentId,
|
|
1820
|
+
parentKey: op2.parentKey
|
|
1821
|
+
};
|
|
1822
|
+
case OpCode.CREATE_OBJECT:
|
|
1823
|
+
return {
|
|
1824
|
+
type: CrdtType.OBJECT,
|
|
1825
|
+
parentId: op2.parentId,
|
|
1826
|
+
parentKey: op2.parentKey,
|
|
1827
|
+
data: op2.data
|
|
1828
|
+
};
|
|
1829
|
+
case OpCode.CREATE_REGISTER:
|
|
1830
|
+
return {
|
|
1831
|
+
type: CrdtType.REGISTER,
|
|
1832
|
+
parentId: op2.parentId,
|
|
1833
|
+
parentKey: op2.parentKey,
|
|
1834
|
+
data: op2.data
|
|
1835
|
+
};
|
|
1836
|
+
default:
|
|
1837
|
+
return assertNever(op2, "Unknown op code");
|
|
1838
|
+
}
|
|
1839
|
+
}
|
|
1840
|
+
var Storage = class {
|
|
1841
|
+
constructor(coreDriver) {
|
|
1842
|
+
// The actual underlying storage API (could be backed by in-memory store,
|
|
1843
|
+
// SQLite, Redis, Postgres, Cloudflare Durable Object Storage, etc.)
|
|
1844
|
+
__publicField(this, "coreDriver");
|
|
1845
|
+
__publicField(this, "_loadedDriver");
|
|
1846
|
+
this.coreDriver = coreDriver;
|
|
1847
|
+
}
|
|
1848
|
+
// -------------------------------------------------------------------------
|
|
1849
|
+
// Public API (for Storage)
|
|
1850
|
+
// -------------------------------------------------------------------------
|
|
1851
|
+
get loadedDriver() {
|
|
1852
|
+
if (this._loadedDriver === void 0) {
|
|
1853
|
+
throw new Error("Cannot access tree before it's been loaded");
|
|
1854
|
+
}
|
|
1855
|
+
return this._loadedDriver;
|
|
1856
|
+
}
|
|
1857
|
+
// REFACTOR NOTE: Eventually raw_iter_nodes has to be removed here
|
|
1858
|
+
raw_iter_nodes() {
|
|
1859
|
+
return this.coreDriver.raw_iter_nodes();
|
|
1860
|
+
}
|
|
1861
|
+
/**
|
|
1862
|
+
* Load the room data from object storage into memory. Persisted room
|
|
1863
|
+
* data consists of the main node map, which represents the Liveblocks
|
|
1864
|
+
* Storage tree, and special keys where we store usage metrics, or room
|
|
1865
|
+
* metadata.
|
|
1866
|
+
*/
|
|
1867
|
+
async load(logger) {
|
|
1868
|
+
this._loadedDriver = await this.coreDriver.load_nodes_api(logger);
|
|
1869
|
+
}
|
|
1870
|
+
unload() {
|
|
1871
|
+
this._loadedDriver = void 0;
|
|
1872
|
+
}
|
|
1873
|
+
/**
|
|
1874
|
+
* Applies a batch of Ops.
|
|
1875
|
+
*/
|
|
1876
|
+
async applyOps(ops) {
|
|
1877
|
+
const results = [];
|
|
1878
|
+
for (const op2 of ops) {
|
|
1879
|
+
results.push(await this.applyOp(op2));
|
|
1880
|
+
}
|
|
1881
|
+
return results;
|
|
1882
|
+
}
|
|
1883
|
+
// -------------------------------------------------------------------------
|
|
1884
|
+
// Private APIs (for Storage)
|
|
1885
|
+
// -------------------------------------------------------------------------
|
|
1886
|
+
/**
|
|
1887
|
+
* Applies a single Op.
|
|
1888
|
+
*/
|
|
1889
|
+
async applyOp(op2) {
|
|
1890
|
+
switch (op2.type) {
|
|
1891
|
+
case OpCode.CREATE_LIST:
|
|
1892
|
+
case OpCode.CREATE_MAP:
|
|
1893
|
+
case OpCode.CREATE_REGISTER:
|
|
1894
|
+
case OpCode.CREATE_OBJECT:
|
|
1895
|
+
return await this.applyCreateOp(op2);
|
|
1896
|
+
case OpCode.UPDATE_OBJECT:
|
|
1897
|
+
return await this.applyUpdateObjectOp(op2);
|
|
1898
|
+
case OpCode.SET_PARENT_KEY:
|
|
1899
|
+
return await this.applySetParentKeyOp(op2);
|
|
1900
|
+
case OpCode.DELETE_OBJECT_KEY:
|
|
1901
|
+
return await this.applyDeleteObjectKeyOp(op2);
|
|
1902
|
+
case OpCode.DELETE_CRDT:
|
|
1903
|
+
return await this.applyDeleteCrdtOp(op2);
|
|
1904
|
+
default:
|
|
1905
|
+
if (process.env.NODE_ENV === "production") {
|
|
1906
|
+
return ignore(op2);
|
|
1907
|
+
} else {
|
|
1908
|
+
return assertNever(op2, "Invalid op");
|
|
1909
|
+
}
|
|
1910
|
+
}
|
|
1911
|
+
}
|
|
1912
|
+
async applyCreateOp(op2) {
|
|
1913
|
+
if (this.loadedDriver.has_node(op2.id)) {
|
|
1914
|
+
return ignore(op2);
|
|
1915
|
+
}
|
|
1916
|
+
const node = nodeFromCreateChildOp(op2);
|
|
1917
|
+
const parent = this.loadedDriver.get_node(node.parentId);
|
|
1918
|
+
if (parent === void 0) {
|
|
1919
|
+
return ignore(op2);
|
|
1920
|
+
}
|
|
1921
|
+
switch (parent.type) {
|
|
1922
|
+
case CrdtType.OBJECT:
|
|
1923
|
+
if (op2.type === OpCode.CREATE_REGISTER) {
|
|
1924
|
+
return ignore(op2);
|
|
1925
|
+
}
|
|
1926
|
+
case CrdtType.MAP:
|
|
1927
|
+
await this.loadedDriver.set_child(op2.id, node, true);
|
|
1928
|
+
return accept(op2);
|
|
1929
|
+
case CrdtType.LIST:
|
|
1930
|
+
return this.createChildAsListItem(op2, node);
|
|
1931
|
+
case CrdtType.REGISTER:
|
|
1932
|
+
return ignore(op2);
|
|
1933
|
+
default:
|
|
1934
|
+
return assertNever(parent, "Unhandled CRDT type");
|
|
1935
|
+
}
|
|
1936
|
+
}
|
|
1937
|
+
async createChildAsListItem(op2, node) {
|
|
1938
|
+
let fix;
|
|
1939
|
+
const intent = _nullishCoalesce(op2.intent, () => ( "insert"));
|
|
1940
|
+
if (intent === "insert") {
|
|
1941
|
+
const insertedParentKey = await this.insertIntoList(op2.id, node);
|
|
1942
|
+
if (insertedParentKey !== node.parentKey) {
|
|
1943
|
+
op2 = { ...op2, parentKey: insertedParentKey };
|
|
1944
|
+
fix = {
|
|
1945
|
+
type: OpCode.SET_PARENT_KEY,
|
|
1946
|
+
id: op2.id,
|
|
1947
|
+
parentKey: insertedParentKey
|
|
1948
|
+
};
|
|
1949
|
+
return accept(op2, fix);
|
|
1950
|
+
}
|
|
1951
|
+
return accept(op2);
|
|
1952
|
+
} else if (intent === "set") {
|
|
1953
|
+
const deletedId = op2.deletedId !== void 0 && op2.deletedId !== op2.id && _optionalChain([this, 'access', _45 => _45.loadedDriver, 'access', _46 => _46.get_node, 'call', _47 => _47(op2.deletedId), 'optionalAccess', _48 => _48.parentId]) === node.parentId ? op2.deletedId : void 0;
|
|
1954
|
+
if (deletedId !== void 0) {
|
|
1955
|
+
await this.loadedDriver.delete_node(deletedId);
|
|
1956
|
+
}
|
|
1957
|
+
const prevItemId = this.loadedDriver.get_child_at(
|
|
1958
|
+
node.parentId,
|
|
1959
|
+
node.parentKey
|
|
1960
|
+
);
|
|
1961
|
+
if (prevItemId !== void 0 && prevItemId !== deletedId) {
|
|
1962
|
+
fix = {
|
|
1963
|
+
type: OpCode.DELETE_CRDT,
|
|
1964
|
+
id: prevItemId
|
|
1965
|
+
};
|
|
1966
|
+
}
|
|
1967
|
+
await this.loadedDriver.set_child(op2.id, node, true);
|
|
1968
|
+
return accept(op2, fix);
|
|
1969
|
+
} else {
|
|
1970
|
+
return assertNever(intent, "Invalid intent");
|
|
1971
|
+
}
|
|
1972
|
+
}
|
|
1973
|
+
async applyDeleteObjectKeyOp(op2) {
|
|
1974
|
+
await this.loadedDriver.delete_child_key(op2.id, op2.key);
|
|
1975
|
+
return accept(op2);
|
|
1976
|
+
}
|
|
1977
|
+
async applyUpdateObjectOp(op2) {
|
|
1978
|
+
await this.loadedDriver.set_object_data(op2.id, op2.data, true);
|
|
1979
|
+
return accept(op2);
|
|
1980
|
+
}
|
|
1981
|
+
async applyDeleteCrdtOp(op2) {
|
|
1982
|
+
await this.loadedDriver.delete_node(op2.id);
|
|
1983
|
+
return accept(op2);
|
|
1984
|
+
}
|
|
1985
|
+
async applySetParentKeyOp(op2) {
|
|
1986
|
+
const newPosition = await this.moveToPosInList(op2.id, op2.parentKey);
|
|
1987
|
+
if (newPosition === void 0) {
|
|
1988
|
+
return ignore(op2);
|
|
1989
|
+
}
|
|
1990
|
+
if (newPosition !== op2.parentKey) {
|
|
1991
|
+
const modifiedOp = { ...op2, parentKey: newPosition };
|
|
1992
|
+
const fix = {
|
|
1993
|
+
type: OpCode.SET_PARENT_KEY,
|
|
1994
|
+
id: op2.id,
|
|
1995
|
+
parentKey: newPosition
|
|
1996
|
+
};
|
|
1997
|
+
return accept(modifiedOp, fix);
|
|
1998
|
+
} else {
|
|
1999
|
+
return accept(op2);
|
|
2000
|
+
}
|
|
2001
|
+
}
|
|
2002
|
+
/**
|
|
2003
|
+
* Inserts a new node in the storage tree, under a list parent. If an
|
|
2004
|
+
* existing sibling node already exist under this key, however, it will look
|
|
2005
|
+
* for another free position under that parent and insert it under
|
|
2006
|
+
* a different parent key that is guaranteed to be available.
|
|
2007
|
+
*
|
|
2008
|
+
* Returns the key that was used for the insertion.
|
|
2009
|
+
*/
|
|
2010
|
+
async insertIntoList(id, node) {
|
|
2011
|
+
const key = this.findFreeListPosition(node.parentId, asPos(node.parentKey));
|
|
2012
|
+
if (key !== node.parentKey) {
|
|
2013
|
+
node = { ...node, parentKey: key };
|
|
2014
|
+
}
|
|
2015
|
+
await this.loadedDriver.set_child(id, node);
|
|
2016
|
+
return node.parentKey;
|
|
2017
|
+
}
|
|
2018
|
+
/**
|
|
2019
|
+
* Tries to move a node to the given position under the same parent. If
|
|
2020
|
+
* a conflicting sibling node already exist at this position, it will use
|
|
2021
|
+
* another free position instead, to avoid the conflict.
|
|
2022
|
+
*
|
|
2023
|
+
* Returns the position (parentKey) that the node was eventually placed at.
|
|
2024
|
+
* If the node could be inserted without conflict, it will return the same
|
|
2025
|
+
* parentKey position.
|
|
2026
|
+
*
|
|
2027
|
+
* Will return `undefined` if this action could not be interpreted. Will be
|
|
2028
|
+
* a no-op for non-list items.
|
|
2029
|
+
*/
|
|
2030
|
+
async moveToPosInList(id, targetKey) {
|
|
2031
|
+
const node = this.loadedDriver.get_node(id);
|
|
2032
|
+
if (_optionalChain([node, 'optionalAccess', _49 => _49.parentId]) === void 0) {
|
|
2033
|
+
return;
|
|
2034
|
+
}
|
|
2035
|
+
if (_optionalChain([this, 'access', _50 => _50.loadedDriver, 'access', _51 => _51.get_node, 'call', _52 => _52(node.parentId), 'optionalAccess', _53 => _53.type]) !== CrdtType.LIST) {
|
|
2036
|
+
return;
|
|
2037
|
+
}
|
|
2038
|
+
if (node.parentKey === targetKey) {
|
|
2039
|
+
return targetKey;
|
|
2040
|
+
}
|
|
2041
|
+
const key = this.findFreeListPosition(node.parentId, asPos(targetKey));
|
|
2042
|
+
if (key !== node.parentKey) {
|
|
2043
|
+
await this.loadedDriver.move_sibling(id, key);
|
|
2044
|
+
}
|
|
2045
|
+
return key;
|
|
2046
|
+
}
|
|
2047
|
+
/**
|
|
2048
|
+
* Checks whether the given parentKey is a "free position" under the
|
|
2049
|
+
* parentId, i.e. there are no siblings that have the same key. If a sibling
|
|
2050
|
+
* exists under that key, it tries to generate new positions until it finds
|
|
2051
|
+
* a free slot, and returns that. The returned value is therefore always safe
|
|
2052
|
+
* to use as parentKey.
|
|
2053
|
+
*/
|
|
2054
|
+
findFreeListPosition(parentId, parentPos) {
|
|
2055
|
+
if (!this.loadedDriver.has_child_at(parentId, parentPos)) {
|
|
2056
|
+
return parentPos;
|
|
2057
|
+
}
|
|
2058
|
+
const currPos = parentPos;
|
|
2059
|
+
const nextPos = this.loadedDriver.get_next_sibling(parentId, currPos);
|
|
2060
|
+
if (nextPos !== void 0) {
|
|
2061
|
+
return makePosition(currPos, nextPos);
|
|
2062
|
+
} else {
|
|
2063
|
+
return makePosition(currPos);
|
|
2064
|
+
}
|
|
2065
|
+
}
|
|
2066
|
+
};
|
|
2067
|
+
|
|
2068
|
+
// src/YjsStorage.ts
|
|
2069
|
+
var _jsbase64 = require('js-base64');
|
|
2070
|
+
|
|
2071
|
+
var _yjs = require('yjs'); var Y = _interopRequireWildcard(_yjs);
|
|
2072
|
+
var MAX_Y_UPDATE_SIZE = 1e5;
|
|
2073
|
+
var YjsStorage = class {
|
|
2074
|
+
constructor(driver) {
|
|
2075
|
+
__publicField(this, "driver");
|
|
2076
|
+
__publicField(this, "doc", new Y.Doc());
|
|
2077
|
+
// the root document
|
|
2078
|
+
__publicField(this, "lastUpdatesById", /* @__PURE__ */ new Map());
|
|
2079
|
+
__publicField(this, "lastSnapshotById", /* @__PURE__ */ new Map());
|
|
2080
|
+
// Keeps track of which keys are loaded, so we can clean them up without calling `.list()`
|
|
2081
|
+
__publicField(this, "keysById", new DefaultMap(
|
|
2082
|
+
() => /* @__PURE__ */ new Set()
|
|
2083
|
+
));
|
|
2084
|
+
__publicField(this, "initPromisesById", /* @__PURE__ */ new Map());
|
|
2085
|
+
/**
|
|
2086
|
+
* Given a record of updates, merge them and compress if savings are significant
|
|
2087
|
+
*/
|
|
2088
|
+
__publicField(this, "_loadAndCompressYJSUpdates", async (docUpdates, doc, docId) => {
|
|
2089
|
+
const SAVINGS_THRESHOLD = 0.2;
|
|
2090
|
+
const updates = Object.values(docUpdates);
|
|
2091
|
+
const sizeOnDisk = updates.reduce((acc, update) => {
|
|
2092
|
+
return acc + update.length;
|
|
2093
|
+
}, 0);
|
|
2094
|
+
if (updates.length > 0) {
|
|
2095
|
+
const docKeys = Object.keys(docUpdates);
|
|
2096
|
+
this.keysById.set(docId, new Set(docKeys));
|
|
2097
|
+
const mergedUpdate = Y.mergeUpdates(updates);
|
|
2098
|
+
Y.applyUpdate(doc, mergedUpdate);
|
|
2099
|
+
const garbageCollectedUpdate = Y.encodeStateAsUpdate(doc);
|
|
2100
|
+
if (garbageCollectedUpdate.length < sizeOnDisk * (1 - SAVINGS_THRESHOLD)) {
|
|
2101
|
+
const newKey = _nanoid.nanoid.call(void 0, );
|
|
2102
|
+
await this.driver.write_y_updates(
|
|
2103
|
+
docId,
|
|
2104
|
+
newKey,
|
|
2105
|
+
garbageCollectedUpdate
|
|
2106
|
+
);
|
|
2107
|
+
await this.driver.delete_y_updates(docId, docKeys);
|
|
2108
|
+
this.keysById.set(docId, /* @__PURE__ */ new Set([newKey]));
|
|
2109
|
+
}
|
|
2110
|
+
}
|
|
2111
|
+
});
|
|
2112
|
+
__publicField(this, "_loadYDocFromDurableStorage", async (doc, docId) => {
|
|
2113
|
+
const docUpdates = Object.fromEntries(
|
|
2114
|
+
await this.driver.iter_y_updates(docId)
|
|
2115
|
+
);
|
|
2116
|
+
await this._loadAndCompressYJSUpdates(docUpdates, doc, docId);
|
|
2117
|
+
this.lastUpdatesById.set(docId, {
|
|
2118
|
+
currentKey: _nanoid.nanoid.call(void 0, ),
|
|
2119
|
+
lastVector: Y.encodeStateVector(doc)
|
|
2120
|
+
});
|
|
2121
|
+
doc.emit("load", [doc]);
|
|
2122
|
+
return doc;
|
|
2123
|
+
});
|
|
2124
|
+
this.driver = driver;
|
|
2125
|
+
this.doc.on("subdocs", ({ removed }) => {
|
|
2126
|
+
removed.forEach((subdoc) => {
|
|
2127
|
+
subdoc.destroy();
|
|
2128
|
+
});
|
|
2129
|
+
});
|
|
2130
|
+
}
|
|
2131
|
+
// ------------------------------------------------------------------------------------
|
|
2132
|
+
// Public API
|
|
2133
|
+
// ------------------------------------------------------------------------------------
|
|
2134
|
+
async getYDoc(docId) {
|
|
2135
|
+
const doc = await this.loadDocByIdIfNotAlreadyLoaded(docId);
|
|
2136
|
+
return doc;
|
|
2137
|
+
}
|
|
2138
|
+
/**
|
|
2139
|
+
* If passed a state vector, an update with diff will be returned, if not the entire doc is returned.
|
|
2140
|
+
*
|
|
2141
|
+
* @param stateVector a base64 encoded target state vector created by running Y.encodeStateVector(Doc) on the client
|
|
2142
|
+
* @returns a base64 encoded array of YJS updates
|
|
2143
|
+
*/
|
|
2144
|
+
async getYDocUpdate(logger, stateVector = "", guid, isV2 = false) {
|
|
2145
|
+
const update = await this.getYDocUpdateBinary(
|
|
2146
|
+
logger,
|
|
2147
|
+
stateVector,
|
|
2148
|
+
guid,
|
|
2149
|
+
isV2
|
|
2150
|
+
);
|
|
2151
|
+
if (!update) return null;
|
|
2152
|
+
return _jsbase64.Base64.fromUint8Array(update);
|
|
2153
|
+
}
|
|
2154
|
+
async getYDocUpdateBinary(logger, stateVector = "", guid, isV2 = false) {
|
|
2155
|
+
const doc = guid !== void 0 ? await this.getYSubdoc(guid) : this.doc;
|
|
2156
|
+
if (!doc) {
|
|
2157
|
+
return null;
|
|
2158
|
+
}
|
|
2159
|
+
let encodedTargetVector;
|
|
2160
|
+
try {
|
|
2161
|
+
encodedTargetVector = stateVector.length > 0 ? _jsbase64.Base64.toUint8Array(stateVector) : void 0;
|
|
2162
|
+
} catch (e) {
|
|
2163
|
+
logger.warn(
|
|
2164
|
+
"Could not get update from passed vector, returning all updates"
|
|
2165
|
+
);
|
|
2166
|
+
}
|
|
2167
|
+
if (isV2) {
|
|
2168
|
+
return Y.encodeStateAsUpdateV2(doc, encodedTargetVector);
|
|
2169
|
+
}
|
|
2170
|
+
return Y.encodeStateAsUpdate(doc, encodedTargetVector);
|
|
2171
|
+
}
|
|
2172
|
+
async getYStateVector(guid) {
|
|
2173
|
+
const doc = guid !== void 0 ? await this.getYSubdoc(guid) : this.doc;
|
|
2174
|
+
if (!doc) {
|
|
2175
|
+
return null;
|
|
2176
|
+
}
|
|
2177
|
+
return _jsbase64.Base64.fromUint8Array(Y.encodeStateVector(doc));
|
|
2178
|
+
}
|
|
2179
|
+
async getSnapshotHash(options) {
|
|
2180
|
+
const doc = options.guid !== void 0 ? await this.getYSubdoc(options.guid) : this.doc;
|
|
2181
|
+
if (!doc) {
|
|
2182
|
+
return null;
|
|
2183
|
+
}
|
|
2184
|
+
const snapshot2 = this._getOrPutLastSnapshot(doc);
|
|
2185
|
+
return this.calculateSnapshotHash(snapshot2, { isV2: options.isV2 });
|
|
2186
|
+
}
|
|
2187
|
+
/**
|
|
2188
|
+
* @param update base64 encoded uint8array
|
|
2189
|
+
* @returns
|
|
2190
|
+
*/
|
|
2191
|
+
async addYDocUpdate(logger, update, guid, isV2) {
|
|
2192
|
+
const doc = guid !== void 0 ? await this.getYSubdoc(guid) : this.doc;
|
|
2193
|
+
if (!doc) {
|
|
2194
|
+
throw new Error(`YDoc with guid ${guid} not found`);
|
|
2195
|
+
}
|
|
2196
|
+
try {
|
|
2197
|
+
const beforeSnapshot = this._getOrPutLastSnapshot(doc);
|
|
2198
|
+
const updateAsU8 = typeof update === "string" ? _jsbase64.Base64.toUint8Array(update) : update;
|
|
2199
|
+
const applyUpdate2 = isV2 ? Y.applyUpdateV2 : Y.applyUpdate;
|
|
2200
|
+
applyUpdate2(doc, updateAsU8, "client");
|
|
2201
|
+
const afterSnapshot = this._putLastSnapshot(doc);
|
|
2202
|
+
const updated = !Y.equalSnapshots(beforeSnapshot, afterSnapshot);
|
|
2203
|
+
if (updated) {
|
|
2204
|
+
await this.handleYDocUpdate(doc);
|
|
2205
|
+
}
|
|
2206
|
+
return {
|
|
2207
|
+
isUpdated: updated,
|
|
2208
|
+
snapshotHash: await this.calculateSnapshotHash(afterSnapshot, { isV2 })
|
|
2209
|
+
};
|
|
2210
|
+
} catch (e) {
|
|
2211
|
+
logger.warn(`Ignored bad YDoc update: ${String(e)}`);
|
|
2212
|
+
throw new Error(
|
|
2213
|
+
"Bad YDoc update. Data is corrupted, or data does not match the encoding."
|
|
2214
|
+
);
|
|
2215
|
+
}
|
|
2216
|
+
}
|
|
2217
|
+
loadDocByIdIfNotAlreadyLoaded(docId) {
|
|
2218
|
+
let loaded$ = this.initPromisesById.get(docId);
|
|
2219
|
+
let doc = docId === ROOT_YDOC_ID ? this.doc : this.findYSubdocByGuid(docId);
|
|
2220
|
+
if (!doc) {
|
|
2221
|
+
doc = new Y.Doc();
|
|
2222
|
+
}
|
|
2223
|
+
if (loaded$ === void 0) {
|
|
2224
|
+
loaded$ = this._loadYDocFromDurableStorage(doc, docId);
|
|
2225
|
+
this.initPromisesById.set(docId, loaded$);
|
|
2226
|
+
}
|
|
2227
|
+
return loaded$;
|
|
2228
|
+
}
|
|
2229
|
+
async load(_logger) {
|
|
2230
|
+
await this.loadDocByIdIfNotAlreadyLoaded(ROOT_YDOC_ID);
|
|
2231
|
+
}
|
|
2232
|
+
/**
|
|
2233
|
+
* Unloads the Yjs documents from memory.
|
|
2234
|
+
*/
|
|
2235
|
+
unload() {
|
|
2236
|
+
}
|
|
2237
|
+
// ------------------------------------------------------------------------------------
|
|
2238
|
+
// Private APIs
|
|
2239
|
+
// ------------------------------------------------------------------------------------
|
|
2240
|
+
// NOTE: We could instead store the hash of snapshot instead of the whole snapshot to optimize memory usage.
|
|
2241
|
+
_getOrPutLastSnapshot(doc) {
|
|
2242
|
+
const docId = doc.guid === this.doc.guid ? ROOT_YDOC_ID : doc.guid;
|
|
2243
|
+
const snapshot2 = this.lastSnapshotById.get(docId);
|
|
2244
|
+
if (snapshot2) {
|
|
2245
|
+
return snapshot2;
|
|
2246
|
+
}
|
|
2247
|
+
return this._putLastSnapshot(doc);
|
|
2248
|
+
}
|
|
2249
|
+
// NOTE: We could instead store the hash of snapshot instead of the whole snapshot to optimize memory usage.
|
|
2250
|
+
_putLastSnapshot(doc) {
|
|
2251
|
+
const docId = doc.guid === this.doc.guid ? ROOT_YDOC_ID : doc.guid;
|
|
2252
|
+
const snapshot2 = Y.snapshot(doc);
|
|
2253
|
+
this.lastSnapshotById.set(docId, snapshot2);
|
|
2254
|
+
return snapshot2;
|
|
2255
|
+
}
|
|
2256
|
+
findYSubdocByGuid(guid) {
|
|
2257
|
+
for (const subdoc of this.doc.getSubdocs()) {
|
|
2258
|
+
if (subdoc.guid === guid) {
|
|
2259
|
+
return subdoc;
|
|
2260
|
+
}
|
|
2261
|
+
}
|
|
2262
|
+
return null;
|
|
2263
|
+
}
|
|
2264
|
+
async calculateSnapshotHash(snapshot2, { isV2 }) {
|
|
2265
|
+
const encodedSnapshot = isV2 ? Y.encodeSnapshotV2(snapshot2) : Y.encodeSnapshot(snapshot2);
|
|
2266
|
+
return _jsbase64.Base64.fromUint8Array(
|
|
2267
|
+
new Uint8Array(
|
|
2268
|
+
await crypto.subtle.digest("SHA-256", new Uint8Array(encodedSnapshot))
|
|
2269
|
+
)
|
|
2270
|
+
);
|
|
2271
|
+
}
|
|
2272
|
+
// gets a subdoc, it will be loaded if not already loaded
|
|
2273
|
+
async getYSubdoc(guid) {
|
|
2274
|
+
const subdoc = this.findYSubdocByGuid(guid);
|
|
2275
|
+
if (!subdoc) {
|
|
2276
|
+
return null;
|
|
2277
|
+
}
|
|
2278
|
+
await this.loadDocByIdIfNotAlreadyLoaded(guid);
|
|
2279
|
+
return subdoc;
|
|
2280
|
+
}
|
|
2281
|
+
// When the YJS doc changes, update it in durable storage
|
|
2282
|
+
async handleYDocUpdate(doc) {
|
|
2283
|
+
const docId = doc.guid === this.doc.guid ? ROOT_YDOC_ID : doc.guid;
|
|
2284
|
+
const docUpdateInfo = this.lastUpdatesById.get(docId);
|
|
2285
|
+
const updateSinceLastVector = Y.encodeStateAsUpdate(
|
|
2286
|
+
doc,
|
|
2287
|
+
_optionalChain([docUpdateInfo, 'optionalAccess', _54 => _54.lastVector])
|
|
2288
|
+
);
|
|
2289
|
+
const storageKey = _nullishCoalesce(_optionalChain([docUpdateInfo, 'optionalAccess', _55 => _55.currentKey]), () => ( _nanoid.nanoid.call(void 0, )));
|
|
2290
|
+
if (updateSinceLastVector.length > MAX_Y_UPDATE_SIZE) {
|
|
2291
|
+
const newKey = _nanoid.nanoid.call(void 0, );
|
|
2292
|
+
await this.driver.write_y_updates(
|
|
2293
|
+
docId,
|
|
2294
|
+
newKey,
|
|
2295
|
+
Y.encodeStateAsUpdate(doc)
|
|
2296
|
+
);
|
|
2297
|
+
await this.driver.delete_y_updates(
|
|
2298
|
+
docId,
|
|
2299
|
+
Array.from(this.keysById.getOrCreate(docId))
|
|
2300
|
+
);
|
|
2301
|
+
this.keysById.set(docId, /* @__PURE__ */ new Set([newKey]));
|
|
2302
|
+
this.lastUpdatesById.set(docId, {
|
|
2303
|
+
currentKey: _nanoid.nanoid.call(void 0, ),
|
|
2304
|
+
// start writing to a new key
|
|
2305
|
+
lastVector: Y.encodeStateVector(doc)
|
|
2306
|
+
});
|
|
2307
|
+
} else {
|
|
2308
|
+
await this.driver.write_y_updates(
|
|
2309
|
+
docId,
|
|
2310
|
+
storageKey,
|
|
2311
|
+
updateSinceLastVector
|
|
2312
|
+
);
|
|
2313
|
+
const keys = [storageKey];
|
|
2314
|
+
const currentKeys = this.keysById.getOrCreate(docId);
|
|
2315
|
+
for (const key of keys) {
|
|
2316
|
+
currentKeys.add(key);
|
|
2317
|
+
}
|
|
2318
|
+
}
|
|
2319
|
+
}
|
|
2320
|
+
};
|
|
2321
|
+
|
|
2322
|
+
// src/lib/tryCatch.ts
|
|
2323
|
+
async function tryCatch(promise) {
|
|
2324
|
+
try {
|
|
2325
|
+
const data = await (typeof promise === "function" ? promise() : promise);
|
|
2326
|
+
return [data, void 0];
|
|
2327
|
+
} catch (error3) {
|
|
2328
|
+
return [void 0, error3];
|
|
2329
|
+
}
|
|
2330
|
+
}
|
|
2331
|
+
|
|
2332
|
+
// src/lib/UniqueMap.ts
|
|
2333
|
+
var __revMap, __keyFn;
|
|
2334
|
+
var UniqueMap = class extends Map {
|
|
2335
|
+
constructor(keyFn) {
|
|
2336
|
+
super();
|
|
2337
|
+
// / \
|
|
2338
|
+
// Primary key Unique key
|
|
2339
|
+
__privateAdd(this, __revMap);
|
|
2340
|
+
__privateAdd(this, __keyFn);
|
|
2341
|
+
__privateSet(this, __keyFn, keyFn);
|
|
2342
|
+
__privateSet(this, __revMap, /* @__PURE__ */ new Map());
|
|
2343
|
+
}
|
|
2344
|
+
lookupPrimaryKey(uniqKey) {
|
|
2345
|
+
return __privateGet(this, __revMap).get(uniqKey);
|
|
2346
|
+
}
|
|
2347
|
+
lookup(uniqKey) {
|
|
2348
|
+
const key = __privateGet(this, __revMap).get(uniqKey);
|
|
2349
|
+
return key !== void 0 ? this.get(key) : void 0;
|
|
2350
|
+
}
|
|
2351
|
+
set(key, value) {
|
|
2352
|
+
const uniqKey = __privateGet(this, __keyFn).call(this, value);
|
|
2353
|
+
const primaryKey = __privateGet(this, __revMap).get(uniqKey);
|
|
2354
|
+
if (primaryKey !== void 0 && primaryKey !== key) {
|
|
2355
|
+
throw new Error(`Unique key ${String(uniqKey)} already exists`);
|
|
2356
|
+
}
|
|
2357
|
+
__privateGet(this, __revMap).set(uniqKey, key);
|
|
2358
|
+
return super.set(key, value);
|
|
2359
|
+
}
|
|
2360
|
+
delete(primaryKey) {
|
|
2361
|
+
const value = this.get(primaryKey);
|
|
2362
|
+
if (value !== void 0) {
|
|
2363
|
+
const indexedKey = __privateGet(this, __keyFn).call(this, value);
|
|
2364
|
+
__privateGet(this, __revMap).delete(indexedKey);
|
|
2365
|
+
}
|
|
2366
|
+
return super.delete(primaryKey);
|
|
2367
|
+
}
|
|
2368
|
+
};
|
|
2369
|
+
__revMap = new WeakMap();
|
|
2370
|
+
__keyFn = new WeakMap();
|
|
2371
|
+
|
|
2372
|
+
// src/utils.ts
|
|
2373
|
+
function concatUint8Arrays(arrays) {
|
|
2374
|
+
const totalLength = arrays.reduce((sum, arr) => sum + arr.length, 0);
|
|
2375
|
+
const result = new Uint8Array(totalLength);
|
|
2376
|
+
let offset = 0;
|
|
2377
|
+
for (const arr of arrays) {
|
|
2378
|
+
result.set(arr, offset);
|
|
2379
|
+
offset += arr.length;
|
|
2380
|
+
}
|
|
2381
|
+
return result;
|
|
2382
|
+
}
|
|
2383
|
+
function makeRoomStateMsg(actor, nonce, scopes, users, publicMeta) {
|
|
2384
|
+
return {
|
|
2385
|
+
type: ServerMsgCode.ROOM_STATE,
|
|
2386
|
+
actor,
|
|
2387
|
+
nonce,
|
|
2388
|
+
scopes,
|
|
2389
|
+
users,
|
|
2390
|
+
meta: _nullishCoalesce(publicMeta, () => ( {}))
|
|
2391
|
+
};
|
|
2392
|
+
}
|
|
2393
|
+
|
|
2394
|
+
// src/Room.ts
|
|
2395
|
+
var messagesDecoder = _decoders.array.call(void 0, clientMsgDecoder);
|
|
2396
|
+
var HIGHEST_PROTOCOL_VERSION = Math.max(
|
|
2397
|
+
...Object.values(ProtocolVersion).filter(
|
|
2398
|
+
(v) => typeof v === "number"
|
|
2399
|
+
)
|
|
2400
|
+
);
|
|
2401
|
+
var SERVER_MSG_CODE_NAMES = Object.fromEntries(
|
|
2402
|
+
Object.entries(ServerMsgCode).map(([k, v]) => [v, k])
|
|
2403
|
+
);
|
|
2404
|
+
var BLACK_HOLE = new Logger([
|
|
2405
|
+
/* No targets, i.e. black hole logger */
|
|
2406
|
+
]);
|
|
2407
|
+
function collectSideEffects() {
|
|
2408
|
+
const deferred = [];
|
|
2409
|
+
return {
|
|
2410
|
+
defer: (p) => void deferred.push(p),
|
|
2411
|
+
waitAll: () => Promise.allSettled(deferred)
|
|
2412
|
+
};
|
|
2413
|
+
}
|
|
2414
|
+
function serialize(msgs) {
|
|
2415
|
+
return JSON.stringify(msgs);
|
|
2416
|
+
}
|
|
2417
|
+
function ackIgnoredOp(opId) {
|
|
2418
|
+
return { type: OpCode.DELETE_CRDT, id: "ACK", opId };
|
|
2419
|
+
}
|
|
2420
|
+
function stripOpId(op2) {
|
|
2421
|
+
const { opId: _, ...rest } = op2;
|
|
2422
|
+
return rest;
|
|
2423
|
+
}
|
|
2424
|
+
var __socket, __debug, __lastActiveAt, __hasNotifiedClientStorageUpdateError;
|
|
2425
|
+
var BrowserSession = class {
|
|
2426
|
+
/** @internal - Never create a BrowserSession instance manually. Use the room.startBrowserSession() API instead. */
|
|
2427
|
+
constructor(ticket, socket, debug) {
|
|
2428
|
+
// ^^ User-defined Session Metadata
|
|
2429
|
+
// ^^ User-defined Client Metadata (sent to client in ROOM_STATE)
|
|
2430
|
+
__publicField(this, "version");
|
|
2431
|
+
// Liveblocks protocol version this client will speak
|
|
2432
|
+
__publicField(this, "actor");
|
|
2433
|
+
// Must be unique within the room
|
|
2434
|
+
__publicField(this, "createdAt");
|
|
2435
|
+
// Externally provided (public!) user metadata. This information will get shared with other clients
|
|
2436
|
+
__publicField(this, "user");
|
|
2437
|
+
__publicField(this, "scopes");
|
|
2438
|
+
// Permissions for this session, sent to connected clients (so consider public info)
|
|
2439
|
+
__publicField(this, "meta");
|
|
2440
|
+
// Arbitrary *private* meta data to attach to this session (will NOT be shared)
|
|
2441
|
+
__publicField(this, "publicMeta");
|
|
2442
|
+
// Metadata sent to client in ROOM_STATE message's "meta" field
|
|
2443
|
+
__privateAdd(this, __socket);
|
|
2444
|
+
__privateAdd(this, __debug);
|
|
2445
|
+
__privateAdd(this, __lastActiveAt);
|
|
2446
|
+
// We keep a status in-memory in the session of whether we already sent a rejected ops message to the client.
|
|
2447
|
+
__privateAdd(this, __hasNotifiedClientStorageUpdateError);
|
|
2448
|
+
this.version = ticket.version;
|
|
2449
|
+
this.actor = ticket.actor;
|
|
2450
|
+
this.user = ticket.user;
|
|
2451
|
+
this.scopes = ticket.scopes;
|
|
2452
|
+
this.meta = _nullishCoalesce(ticket.meta, () => ( void 0));
|
|
2453
|
+
this.publicMeta = ticket.publicMeta;
|
|
2454
|
+
__privateSet(this, __socket, socket);
|
|
2455
|
+
__privateSet(this, __debug, debug);
|
|
2456
|
+
const now = /* @__PURE__ */ new Date();
|
|
2457
|
+
this.createdAt = now;
|
|
2458
|
+
__privateSet(this, __lastActiveAt, now);
|
|
2459
|
+
__privateSet(this, __hasNotifiedClientStorageUpdateError, false);
|
|
2460
|
+
}
|
|
2461
|
+
get lastActiveAt() {
|
|
2462
|
+
const lastPing = _optionalChain([__privateGet, 'call', _56 => _56(this, __socket), 'access', _57 => _57.getLastPongTimestamp, 'optionalCall', _58 => _58()]);
|
|
2463
|
+
if (lastPing && lastPing > __privateGet(this, __lastActiveAt)) {
|
|
2464
|
+
return lastPing;
|
|
2465
|
+
} else {
|
|
2466
|
+
return __privateGet(this, __lastActiveAt);
|
|
2467
|
+
}
|
|
2468
|
+
}
|
|
2469
|
+
get hasNotifiedClientStorageUpdateError() {
|
|
2470
|
+
return __privateGet(this, __hasNotifiedClientStorageUpdateError);
|
|
2471
|
+
}
|
|
2472
|
+
markActive(now = /* @__PURE__ */ new Date()) {
|
|
2473
|
+
if (now > __privateGet(this, __lastActiveAt)) {
|
|
2474
|
+
__privateSet(this, __lastActiveAt, now);
|
|
2475
|
+
}
|
|
2476
|
+
}
|
|
2477
|
+
setHasNotifiedClientStorageUpdateError() {
|
|
2478
|
+
__privateSet(this, __hasNotifiedClientStorageUpdateError, true);
|
|
2479
|
+
}
|
|
2480
|
+
sendPong() {
|
|
2481
|
+
this.markActive();
|
|
2482
|
+
const sent = __privateGet(this, __socket).send("pong");
|
|
2483
|
+
if (__privateGet(this, __debug)) {
|
|
2484
|
+
if (sent < 0) {
|
|
2485
|
+
console.error(
|
|
2486
|
+
`failed to send "pong" to actor=${this.actor} (back pressure)`
|
|
2487
|
+
);
|
|
2488
|
+
} else if (sent === 0) {
|
|
2489
|
+
console.error(
|
|
2490
|
+
`failed to send "pong" to actor=${this.actor} (connection issue)`
|
|
2491
|
+
);
|
|
2492
|
+
} else {
|
|
2493
|
+
console.log(`sent to actor=${this.actor}: "pong"`);
|
|
2494
|
+
}
|
|
2495
|
+
}
|
|
2496
|
+
return sent;
|
|
2497
|
+
}
|
|
2498
|
+
send(serverMsg) {
|
|
2499
|
+
const data = typeof serverMsg === "string" ? serverMsg : serialize(serverMsg);
|
|
2500
|
+
const sent = __privateGet(this, __socket).send(data);
|
|
2501
|
+
if (__privateGet(this, __debug)) {
|
|
2502
|
+
if (sent < 0) {
|
|
2503
|
+
console.error(
|
|
2504
|
+
`failed to send message to actor=${this.actor} (back pressure)`
|
|
2505
|
+
);
|
|
2506
|
+
} else if (sent === 0) {
|
|
2507
|
+
console.error(
|
|
2508
|
+
`failed to send message to actor=${this.actor} (connection issue)`
|
|
2509
|
+
);
|
|
2510
|
+
}
|
|
2511
|
+
const msgs = JSON.parse(data);
|
|
2512
|
+
for (const msg of Array.isArray(msgs) ? msgs : [msgs]) {
|
|
2513
|
+
console.log(
|
|
2514
|
+
`sent to actor=${this.actor}: [${_nullishCoalesce(SERVER_MSG_CODE_NAMES[msg.type], () => ( msg.type))}] ${JSON.stringify(msg)}`
|
|
2515
|
+
);
|
|
2516
|
+
}
|
|
2517
|
+
}
|
|
2518
|
+
return sent;
|
|
2519
|
+
}
|
|
2520
|
+
/**
|
|
2521
|
+
* @internal
|
|
2522
|
+
* Closes the socket associated to this BrowserSession.
|
|
2523
|
+
*
|
|
2524
|
+
* NOTE: Never call this API directly! Call .endBrowserSession() instead.
|
|
2525
|
+
*/
|
|
2526
|
+
closeSocket(code, reason) {
|
|
2527
|
+
__privateGet(this, __socket).close(code, reason);
|
|
2528
|
+
}
|
|
2529
|
+
};
|
|
2530
|
+
__socket = new WeakMap();
|
|
2531
|
+
__debug = new WeakMap();
|
|
2532
|
+
__lastActiveAt = new WeakMap();
|
|
2533
|
+
__hasNotifiedClientStorageUpdateError = new WeakMap();
|
|
2534
|
+
var BackendSession = class extends BrowserSession {
|
|
2535
|
+
/** @internal Never call this constructor directly */
|
|
2536
|
+
constructor(ticket, socket, debug) {
|
|
2537
|
+
super(ticket, socket, debug);
|
|
2538
|
+
}
|
|
2539
|
+
};
|
|
2540
|
+
var __debug2, __allowStreaming;
|
|
2541
|
+
var Room = class {
|
|
2542
|
+
constructor(meta, options) {
|
|
2543
|
+
// ^^^^^^^^^^ User-defined Room Metadata, Session Metadata, and Client Metadata
|
|
2544
|
+
__publicField(this, "meta");
|
|
2545
|
+
__publicField(this, "driver");
|
|
2546
|
+
__publicField(this, "logger");
|
|
2547
|
+
__publicField(this, "_loadData$", null);
|
|
2548
|
+
__publicField(this, "_data", null);
|
|
2549
|
+
__publicField(this, "_qsize", 0);
|
|
2550
|
+
__publicField(this, "sessions", new UniqueMap((s) => s.actor));
|
|
2551
|
+
__publicField(this, "hooks");
|
|
2552
|
+
__privateAdd(this, __debug2);
|
|
2553
|
+
__privateAdd(this, __allowStreaming);
|
|
2554
|
+
const driver = _nullishCoalesce(_optionalChain([options, 'optionalAccess', _59 => _59.storage]), () => ( makeNewInMemoryDriver()));
|
|
2555
|
+
this.meta = meta;
|
|
2556
|
+
this.driver = driver;
|
|
2557
|
+
this.logger = _nullishCoalesce(_optionalChain([options, 'optionalAccess', _60 => _60.logger]), () => ( BLACK_HOLE));
|
|
2558
|
+
__privateSet(this, __allowStreaming, _nullishCoalesce(_optionalChain([options, 'optionalAccess', _61 => _61.allowStreaming]), () => ( true)));
|
|
2559
|
+
this.hooks = {
|
|
2560
|
+
isClientMsgAllowed: _nullishCoalesce(_optionalChain([options, 'optionalAccess', _62 => _62.hooks, 'optionalAccess', _63 => _63.isClientMsgAllowed]), () => ( (() => {
|
|
2561
|
+
return {
|
|
2562
|
+
allowed: true
|
|
2563
|
+
};
|
|
2564
|
+
}))),
|
|
2565
|
+
// YYY .load() isn't called on the RoomServer yet! As soon as it does, these hooks will get called
|
|
2566
|
+
onRoomWillLoad: _optionalChain([options, 'optionalAccess', _64 => _64.hooks, 'optionalAccess', _65 => _65.onRoomWillLoad]),
|
|
2567
|
+
onRoomDidLoad: _optionalChain([options, 'optionalAccess', _66 => _66.hooks, 'optionalAccess', _67 => _67.onRoomDidLoad]),
|
|
2568
|
+
onRoomWillUnload: _optionalChain([options, 'optionalAccess', _68 => _68.hooks, 'optionalAccess', _69 => _69.onRoomWillUnload]),
|
|
2569
|
+
onRoomDidUnload: _optionalChain([options, 'optionalAccess', _70 => _70.hooks, 'optionalAccess', _71 => _71.onRoomDidUnload]),
|
|
2570
|
+
onSessionDidStart: _optionalChain([options, 'optionalAccess', _72 => _72.hooks, 'optionalAccess', _73 => _73.onSessionDidStart]),
|
|
2571
|
+
onSessionDidEnd: _optionalChain([options, 'optionalAccess', _74 => _74.hooks, 'optionalAccess', _75 => _75.onSessionDidEnd]),
|
|
2572
|
+
postClientMsgStorageDidUpdate: _optionalChain([options, 'optionalAccess', _76 => _76.hooks, 'optionalAccess', _77 => _77.postClientMsgStorageDidUpdate]),
|
|
2573
|
+
postClientMsgYdocDidUpdate: _optionalChain([options, 'optionalAccess', _78 => _78.hooks, 'optionalAccess', _79 => _79.postClientMsgYdocDidUpdate])
|
|
2574
|
+
};
|
|
2575
|
+
__privateSet(this, __debug2, _nullishCoalesce(_optionalChain([options, 'optionalAccess', _80 => _80.enableDebugLogging]), () => ( false)));
|
|
2576
|
+
}
|
|
2577
|
+
get loadingState() {
|
|
2578
|
+
if (this._loadData$ === null) {
|
|
2579
|
+
return "initial";
|
|
2580
|
+
} else if (this._data === null) {
|
|
2581
|
+
return "loading";
|
|
2582
|
+
} else {
|
|
2583
|
+
return "loaded";
|
|
2584
|
+
}
|
|
2585
|
+
}
|
|
2586
|
+
get numSessions() {
|
|
2587
|
+
return this.sessions.size;
|
|
2588
|
+
}
|
|
2589
|
+
// prettier-ignore
|
|
2590
|
+
get storage() {
|
|
2591
|
+
return this.data.storage;
|
|
2592
|
+
}
|
|
2593
|
+
// prettier-ignore
|
|
2594
|
+
get yjsStorage() {
|
|
2595
|
+
return this.data.yjsStorage;
|
|
2596
|
+
}
|
|
2597
|
+
// prettier-ignore
|
|
2598
|
+
get mutex() {
|
|
2599
|
+
return this.data.mutex;
|
|
2600
|
+
}
|
|
2601
|
+
// prettier-ignore
|
|
2602
|
+
get data() {
|
|
2603
|
+
return _nullishCoalesce(this._data, () => ( raise("Cannot use room before it's loaded")));
|
|
2604
|
+
}
|
|
2605
|
+
// prettier-ignore
|
|
2606
|
+
// ------------------------------------------------------------------------------------
|
|
2607
|
+
// Public API
|
|
2608
|
+
// ------------------------------------------------------------------------------------
|
|
2609
|
+
/**
|
|
2610
|
+
* Initializes the Room, so it's ready to start accepting connections. Safe
|
|
2611
|
+
* to call multiple times. After awaiting `room.load()` the Room is ready to
|
|
2612
|
+
* be used.
|
|
2613
|
+
*/
|
|
2614
|
+
async load(ctx) {
|
|
2615
|
+
if (this._loadData$ === null) {
|
|
2616
|
+
this._data = null;
|
|
2617
|
+
this._loadData$ = this._load(ctx).catch((e) => {
|
|
2618
|
+
this._data = null;
|
|
2619
|
+
this._loadData$ = null;
|
|
2620
|
+
throw e;
|
|
2621
|
+
});
|
|
2622
|
+
}
|
|
2623
|
+
return this._loadData$;
|
|
2624
|
+
}
|
|
2625
|
+
/**
|
|
2626
|
+
* Releases the currently-loaded storage tree from worker memory, freeing it
|
|
2627
|
+
* up to be garbage collected. The next time a user will join the room, the
|
|
2628
|
+
* room will be reloaded from storage.
|
|
2629
|
+
*/
|
|
2630
|
+
unload(ctx) {
|
|
2631
|
+
_optionalChain([this, 'access', _81 => _81.hooks, 'access', _82 => _82.onRoomWillUnload, 'optionalCall', _83 => _83(ctx)]);
|
|
2632
|
+
if (this._data) {
|
|
2633
|
+
this.storage.unload();
|
|
2634
|
+
this.yjsStorage.unload();
|
|
2635
|
+
}
|
|
2636
|
+
this._loadData$ = null;
|
|
2637
|
+
_optionalChain([this, 'access', _84 => _84.hooks, 'access', _85 => _85.onRoomDidUnload, 'optionalCall', _86 => _86(ctx)]);
|
|
2638
|
+
}
|
|
2639
|
+
/**
|
|
2640
|
+
* Issues a Ticket with a new/unique actor ID
|
|
2641
|
+
*
|
|
2642
|
+
* IMPORTANT! As the caller of this function, you are responsible for
|
|
2643
|
+
* ensuring you trust the values passed in here. Never pass unauthorized
|
|
2644
|
+
* values in here.
|
|
2645
|
+
*
|
|
2646
|
+
* The returned Ticket can be turned into a active Session once the socket
|
|
2647
|
+
* connection is established. If the socket is never established, this
|
|
2648
|
+
* unused Ticket will simply get garbage collected.
|
|
2649
|
+
*/
|
|
2650
|
+
async createTicket(options) {
|
|
2651
|
+
const actor$ = _nullishCoalesce(_optionalChain([options, 'optionalAccess', _87 => _87.actor]), () => ( this.getNextActor()));
|
|
2652
|
+
const sessionKey = _nanoid.nanoid.call(void 0, );
|
|
2653
|
+
const info = _optionalChain([options, 'optionalAccess', _88 => _88.info]);
|
|
2654
|
+
const ticket = {
|
|
2655
|
+
version: _nullishCoalesce(_optionalChain([options, 'optionalAccess', _89 => _89.version]), () => ( HIGHEST_PROTOCOL_VERSION)),
|
|
2656
|
+
actor: await actor$,
|
|
2657
|
+
sessionKey,
|
|
2658
|
+
meta: _optionalChain([options, 'optionalAccess', _90 => _90.meta]),
|
|
2659
|
+
publicMeta: _optionalChain([options, 'optionalAccess', _91 => _91.publicMeta]),
|
|
2660
|
+
user: _optionalChain([options, 'optionalAccess', _92 => _92.id]) ? { id: options.id, info } : { anonymousId: _nullishCoalesce(_optionalChain([options, 'optionalAccess', _93 => _93.anonymousId]), () => ( _nanoid.nanoid.call(void 0, ))), info },
|
|
2661
|
+
scopes: _nullishCoalesce(_optionalChain([options, 'optionalAccess', _94 => _94.scopes]), () => ( ["room:write"]))
|
|
2662
|
+
};
|
|
2663
|
+
if (__privateGet(this, __debug2)) {
|
|
2664
|
+
console.log(`new ticket created: ${JSON.stringify(ticket)}`);
|
|
2665
|
+
}
|
|
2666
|
+
return ticket;
|
|
2667
|
+
}
|
|
2668
|
+
async createBackendSession_experimental() {
|
|
2669
|
+
const ticket = await this.createTicket();
|
|
2670
|
+
const capturedServerMsgs = [];
|
|
2671
|
+
const stub = {
|
|
2672
|
+
send: (data) => {
|
|
2673
|
+
if (typeof data === "string") {
|
|
2674
|
+
capturedServerMsgs.push(data);
|
|
2675
|
+
}
|
|
2676
|
+
return 0;
|
|
2677
|
+
},
|
|
2678
|
+
close: () => {
|
|
2679
|
+
}
|
|
2680
|
+
// noop
|
|
2681
|
+
};
|
|
2682
|
+
const session = new BackendSession(ticket, stub, false);
|
|
2683
|
+
return [session, capturedServerMsgs];
|
|
2684
|
+
}
|
|
2685
|
+
/**
|
|
2686
|
+
* Restores the given sessions as the Room server's session list. Can only be
|
|
2687
|
+
* called as long as there are no existing sessions.
|
|
2688
|
+
*
|
|
2689
|
+
* The key difference with the .startBrowserSession() API is that restoreSessions is
|
|
2690
|
+
* used in cases where a session was hibernated and needs to be restored,
|
|
2691
|
+
* without _conceptually_ starting a new session.
|
|
2692
|
+
*
|
|
2693
|
+
* Because there are no side effects to restoreSession, it's synchronous.
|
|
2694
|
+
*/
|
|
2695
|
+
restoreSessions(sessions) {
|
|
2696
|
+
if (this.sessions.size > 0) {
|
|
2697
|
+
throw new Error("This API can only be called before any sessions exist");
|
|
2698
|
+
}
|
|
2699
|
+
for (const { ticket, socket, lastActivity } of sessions) {
|
|
2700
|
+
const newSession = new BrowserSession(ticket, socket, __privateGet(this, __debug2));
|
|
2701
|
+
this.sessions.set(ticket.sessionKey, newSession);
|
|
2702
|
+
newSession.markActive(lastActivity);
|
|
2703
|
+
}
|
|
2704
|
+
}
|
|
2705
|
+
/**
|
|
2706
|
+
* Registers a new BrowserSession into the Room server's session list, along with
|
|
2707
|
+
* the socket connection to use for that BrowserSession, now that it is known.
|
|
2708
|
+
*
|
|
2709
|
+
* This kicks off a few side effects:
|
|
2710
|
+
* - Sends a ROOM_STATE message to the socket.
|
|
2711
|
+
* - Broadcasts a USER_JOINED message to all other sessions in the room.
|
|
2712
|
+
*/
|
|
2713
|
+
startBrowserSession(ticket, socket, ctx, defer = () => {
|
|
2714
|
+
throw new Error(
|
|
2715
|
+
"One of your hook handlers returned a promise, but no side effect collector was provided. Pass a `defer` callback to startBrowserSession() to collect async side effects."
|
|
2716
|
+
);
|
|
2717
|
+
}) {
|
|
2718
|
+
let existing;
|
|
2719
|
+
while ((existing = this.sessions.lookupPrimaryKey(ticket.actor)) !== void 0) {
|
|
2720
|
+
this.endBrowserSession(
|
|
2721
|
+
existing,
|
|
2722
|
+
WebsocketCloseCodes.KICKED,
|
|
2723
|
+
"Closed stale connection",
|
|
2724
|
+
ctx,
|
|
2725
|
+
defer
|
|
2726
|
+
);
|
|
2727
|
+
this.logger.warn(
|
|
2728
|
+
`Previous session for actor ${ticket.actor} killed in favor of new session`
|
|
2729
|
+
);
|
|
2730
|
+
}
|
|
2731
|
+
const newSession = new BrowserSession(ticket, socket, __privateGet(this, __debug2));
|
|
2732
|
+
this.sessions.set(ticket.sessionKey, newSession);
|
|
2733
|
+
const users = {};
|
|
2734
|
+
for (const session of this.otherSessions(ticket.sessionKey)) {
|
|
2735
|
+
users[session.actor] = {
|
|
2736
|
+
id: session.user.id,
|
|
2737
|
+
info: session.user.info,
|
|
2738
|
+
scopes: session.scopes
|
|
2739
|
+
};
|
|
2740
|
+
}
|
|
2741
|
+
newSession.send(
|
|
2742
|
+
makeRoomStateMsg(
|
|
2743
|
+
newSession.actor,
|
|
2744
|
+
ticket.sessionKey,
|
|
2745
|
+
// called "nonce" in the protocol
|
|
2746
|
+
newSession.scopes,
|
|
2747
|
+
users,
|
|
2748
|
+
ticket.publicMeta
|
|
2749
|
+
)
|
|
2750
|
+
);
|
|
2751
|
+
this.sendToOthers(
|
|
2752
|
+
ticket.sessionKey,
|
|
2753
|
+
{
|
|
2754
|
+
type: ServerMsgCode.USER_JOINED,
|
|
2755
|
+
actor: newSession.actor,
|
|
2756
|
+
id: newSession.user.id,
|
|
2757
|
+
info: newSession.user.info,
|
|
2758
|
+
scopes: newSession.scopes
|
|
2759
|
+
},
|
|
2760
|
+
ctx,
|
|
2761
|
+
defer
|
|
2762
|
+
);
|
|
2763
|
+
const p$ = _optionalChain([this, 'access', _95 => _95.hooks, 'access', _96 => _96.onSessionDidStart, 'optionalCall', _97 => _97(newSession, ctx)]);
|
|
2764
|
+
if (p$) defer(p$);
|
|
2765
|
+
}
|
|
2766
|
+
/**
|
|
2767
|
+
* Unregisters the BrowserSession for the given actor. Call this when the socket has
|
|
2768
|
+
* been closed from the client's end.
|
|
2769
|
+
*
|
|
2770
|
+
* This kicks off a few side effects:
|
|
2771
|
+
* - Broadcasts a USER_LEFT message to all other sessions in the room.
|
|
2772
|
+
*/
|
|
2773
|
+
endBrowserSession(key, code, reason, ctx, defer = () => {
|
|
2774
|
+
throw new Error(
|
|
2775
|
+
"Your onSessionDidEnd handler returned a promise, but no side effect collector was provided. Pass a `defer` callback to endBrowserSession() to collect async side effects."
|
|
2776
|
+
);
|
|
2777
|
+
}) {
|
|
2778
|
+
const sessions = this.sessions;
|
|
2779
|
+
const session = sessions.get(key);
|
|
2780
|
+
if (session === void 0) return;
|
|
2781
|
+
session.closeSocket(code, reason);
|
|
2782
|
+
const deleted = sessions.delete(key);
|
|
2783
|
+
if (deleted) {
|
|
2784
|
+
for (const other of this.otherSessions(key)) {
|
|
2785
|
+
other.send({ type: ServerMsgCode.USER_LEFT, actor: session.actor });
|
|
2786
|
+
}
|
|
2787
|
+
const p$ = _optionalChain([this, 'access', _98 => _98.hooks, 'access', _99 => _99.onSessionDidEnd, 'optionalCall', _100 => _100(session, ctx)]);
|
|
2788
|
+
if (p$) defer(p$);
|
|
2789
|
+
}
|
|
2790
|
+
}
|
|
2791
|
+
/**
|
|
2792
|
+
* Force-closes all sessions matching the given predicate.
|
|
2793
|
+
*/
|
|
2794
|
+
endSessionBy(predicate, code, reason, ctx, defer = () => {
|
|
2795
|
+
throw new Error(
|
|
2796
|
+
"Your onSessionDidEnd handler returned a promise, but no side effect collector was provided. Pass a `defer` callback to endSessionBy() to collect async side effects."
|
|
2797
|
+
);
|
|
2798
|
+
}) {
|
|
2799
|
+
let count = 0;
|
|
2800
|
+
for (const [key, session] of this.sessions) {
|
|
2801
|
+
if (predicate(session)) {
|
|
2802
|
+
count++;
|
|
2803
|
+
this.endBrowserSession(key, code, reason, ctx, defer);
|
|
2804
|
+
}
|
|
2805
|
+
}
|
|
2806
|
+
return count;
|
|
2807
|
+
}
|
|
2808
|
+
/**
|
|
2809
|
+
* Handles a raw incoming socket message, which can be a ping, or an
|
|
2810
|
+
* JSON-encoded message batch.
|
|
2811
|
+
*/
|
|
2812
|
+
async handleData(key, data, ctx, defer = () => {
|
|
2813
|
+
throw new Error(
|
|
2814
|
+
"One of your hook handlers returned a promise, but no side effect collector was provided. Pass a `defer` callback to handleData() to collect async side effects."
|
|
2815
|
+
);
|
|
2816
|
+
}) {
|
|
2817
|
+
const text = typeof data === "string" ? data : raise("Unsupported message format");
|
|
2818
|
+
if (text === "ping") {
|
|
2819
|
+
await this.handlePing(key, ctx);
|
|
2820
|
+
} else {
|
|
2821
|
+
const json = tryParseJson(text);
|
|
2822
|
+
const messages = messagesDecoder.decode(json);
|
|
2823
|
+
if (!messages.ok) {
|
|
2824
|
+
const reason = process.env.NODE_ENV !== "production" ? _decoders.formatInline.call(void 0, messages.error) : "Invalid message format";
|
|
2825
|
+
this.endBrowserSession(
|
|
2826
|
+
key,
|
|
2827
|
+
WebsocketCloseCodes.INVALID_MESSAGE_FORMAT,
|
|
2828
|
+
reason,
|
|
2829
|
+
ctx,
|
|
2830
|
+
defer
|
|
2831
|
+
);
|
|
2832
|
+
return;
|
|
2833
|
+
}
|
|
2834
|
+
if (this._qsize > 1e4) {
|
|
2835
|
+
} else if (this._qsize > 5e3) {
|
|
2836
|
+
}
|
|
2837
|
+
this._qsize++;
|
|
2838
|
+
try {
|
|
2839
|
+
await this.processClientMsg(key, messages.value, ctx);
|
|
2840
|
+
} finally {
|
|
2841
|
+
this._qsize--;
|
|
2842
|
+
}
|
|
2843
|
+
}
|
|
2844
|
+
}
|
|
2845
|
+
/**
|
|
2846
|
+
* Processes an incoming batch of 1 or more ClientMsgs on behalf of
|
|
2847
|
+
* a (regular user/browser) session.
|
|
2848
|
+
*
|
|
2849
|
+
* IMPORTANT: Only use this API on "trusted" data!
|
|
2850
|
+
* To handle untrusted input data, use `.handleData()` instead.
|
|
2851
|
+
*
|
|
2852
|
+
* Before calling this API, make sure:
|
|
2853
|
+
* 1. The call site is entitled to call this message on behalf of this session; and
|
|
2854
|
+
* 2. The ClientMsg payload has been validated to be correct.
|
|
2855
|
+
*/
|
|
2856
|
+
async processClientMsg(key, messages, ctx) {
|
|
2857
|
+
await this.load(ctx);
|
|
2858
|
+
const { defer, waitAll } = collectSideEffects();
|
|
2859
|
+
await this.mutex.runExclusive(
|
|
2860
|
+
() => this._processClientMsg_withExclusiveAccess(key, messages, ctx, defer)
|
|
2861
|
+
);
|
|
2862
|
+
await waitAll();
|
|
2863
|
+
}
|
|
2864
|
+
/**
|
|
2865
|
+
* Processes an incoming batch of 1 or more ClientMsgs on behalf of
|
|
2866
|
+
* a BACKEND session.
|
|
2867
|
+
*
|
|
2868
|
+
* Difference 1: HTTP RESPONSE instead of WEB SOCKET RESPONSE
|
|
2869
|
+
* ----------------------------------------------------------
|
|
2870
|
+
* For "normal" sessions that have a socket attached, any "responses" (i.e.
|
|
2871
|
+
* server messages like acks or fixops) will be sent back through that
|
|
2872
|
+
* existing socket connection.
|
|
2873
|
+
*
|
|
2874
|
+
* The key difference when using this method is that there is no such socket,
|
|
2875
|
+
* so any "response" ServerMsgs will get sent back as an HTTP response.
|
|
2876
|
+
*
|
|
2877
|
+
* Difference 2: No auth check
|
|
2878
|
+
* ---------------------------
|
|
2879
|
+
* Another key difference is that when processing a backend session, no
|
|
2880
|
+
* "isClientMsgAllowed()" check is performed, because those checks assume
|
|
2881
|
+
* a session.
|
|
2882
|
+
*/
|
|
2883
|
+
async processClientMsgFromBackendSession(session, messages, ctx) {
|
|
2884
|
+
await this.load(ctx);
|
|
2885
|
+
const { defer, waitAll } = collectSideEffects();
|
|
2886
|
+
await this.mutex.runExclusive(
|
|
2887
|
+
() => this._processClientMsgFromBackendSession_withExclusiveAccess(
|
|
2888
|
+
session,
|
|
2889
|
+
messages,
|
|
2890
|
+
ctx,
|
|
2891
|
+
defer
|
|
2892
|
+
)
|
|
2893
|
+
);
|
|
2894
|
+
await waitAll();
|
|
2895
|
+
}
|
|
2896
|
+
getSession(sessionKey) {
|
|
2897
|
+
return this.sessions.get(sessionKey);
|
|
2898
|
+
}
|
|
2899
|
+
listSessions() {
|
|
2900
|
+
return Array.from(this.sessions.values());
|
|
2901
|
+
}
|
|
2902
|
+
/**
|
|
2903
|
+
* Will send the given ServerMsg to all Sessions, except the Session
|
|
2904
|
+
* where the message originates from.
|
|
2905
|
+
*/
|
|
2906
|
+
sendToOthers(sender, serverMsg, ctx, defer = () => {
|
|
2907
|
+
throw new Error(
|
|
2908
|
+
"One of your hook handlers returned a promise, but no side effect collector was provided. Pass a `defer` callback to sendToOthers() to collect async side effects."
|
|
2909
|
+
);
|
|
2910
|
+
}) {
|
|
2911
|
+
const msg = serialize(serverMsg);
|
|
2912
|
+
for (const [key, session] of this.otherSessionEntries(sender)) {
|
|
2913
|
+
const success = session.send(msg);
|
|
2914
|
+
if (success === 0) {
|
|
2915
|
+
this.endBrowserSession(
|
|
2916
|
+
key,
|
|
2917
|
+
WebsocketCloseCodes.KICKED,
|
|
2918
|
+
"Closed broken connection",
|
|
2919
|
+
ctx,
|
|
2920
|
+
defer
|
|
2921
|
+
);
|
|
2922
|
+
}
|
|
2923
|
+
}
|
|
2924
|
+
}
|
|
2925
|
+
/**
|
|
2926
|
+
* Will broadcast the given ServerMsg to all Sessions in the Room.
|
|
2927
|
+
*/
|
|
2928
|
+
sendToAll(serverMsg, ctx, defer = () => {
|
|
2929
|
+
throw new Error(
|
|
2930
|
+
"One of your hook handlers returned a promise, but no side effect collector was provided. Pass a `defer` callback to sendToAll() to collect async side effects."
|
|
2931
|
+
);
|
|
2932
|
+
}) {
|
|
2933
|
+
const msg = serialize(serverMsg);
|
|
2934
|
+
for (const [key, session] of this.sessions) {
|
|
2935
|
+
const success = session.send(msg);
|
|
2936
|
+
if (success === 0) {
|
|
2937
|
+
this.endBrowserSession(
|
|
2938
|
+
key,
|
|
2939
|
+
WebsocketCloseCodes.KICKED,
|
|
2940
|
+
"Closed broken connection",
|
|
2941
|
+
ctx,
|
|
2942
|
+
defer
|
|
2943
|
+
);
|
|
2944
|
+
}
|
|
2945
|
+
}
|
|
2946
|
+
}
|
|
2947
|
+
// ------------------------------------------------------------------------------------
|
|
2948
|
+
// Private APIs
|
|
2949
|
+
// ------------------------------------------------------------------------------------
|
|
2950
|
+
async _loadStorage() {
|
|
2951
|
+
const storage = new Storage(this.driver);
|
|
2952
|
+
await storage.load(this.logger);
|
|
2953
|
+
return storage;
|
|
2954
|
+
}
|
|
2955
|
+
async _loadYjsStorage() {
|
|
2956
|
+
const yjsStorage = new YjsStorage(this.driver);
|
|
2957
|
+
await yjsStorage.load(this.logger);
|
|
2958
|
+
return yjsStorage;
|
|
2959
|
+
}
|
|
2960
|
+
// Don't ever manually call this!
|
|
2961
|
+
async _load(ctx) {
|
|
2962
|
+
await _optionalChain([this, 'access', _101 => _101.hooks, 'access', _102 => _102.onRoomWillLoad, 'optionalCall', _103 => _103(ctx)]);
|
|
2963
|
+
const storage = await this._loadStorage();
|
|
2964
|
+
const yjsStorage = await this._loadYjsStorage();
|
|
2965
|
+
this._data = {
|
|
2966
|
+
mutex: new (0, _asyncmutex.Mutex)(),
|
|
2967
|
+
storage,
|
|
2968
|
+
yjsStorage
|
|
2969
|
+
};
|
|
2970
|
+
await _optionalChain([this, 'access', _104 => _104.hooks, 'access', _105 => _105.onRoomDidLoad, 'optionalCall', _106 => _106(ctx)]);
|
|
2971
|
+
}
|
|
2972
|
+
/**
|
|
2973
|
+
* Returns a new, unique, actor ID.
|
|
2974
|
+
*/
|
|
2975
|
+
async getNextActor() {
|
|
2976
|
+
return await this.driver.next_actor();
|
|
2977
|
+
}
|
|
2978
|
+
/**
|
|
2979
|
+
* Iterates over all *other* Sessions and their session keys.
|
|
2980
|
+
*/
|
|
2981
|
+
*otherSessionEntries(currentKey) {
|
|
2982
|
+
for (const [key, session] of this.sessions) {
|
|
2983
|
+
if (key !== currentKey) {
|
|
2984
|
+
yield [key, session];
|
|
2985
|
+
}
|
|
2986
|
+
}
|
|
2987
|
+
}
|
|
2988
|
+
/**
|
|
2989
|
+
* Iterates over all *other* Sessions.
|
|
2990
|
+
*/
|
|
2991
|
+
*otherSessions(currentKey) {
|
|
2992
|
+
for (const [key, session] of this.sessions) {
|
|
2993
|
+
if (key !== currentKey) {
|
|
2994
|
+
yield session;
|
|
2995
|
+
}
|
|
2996
|
+
}
|
|
2997
|
+
}
|
|
2998
|
+
/**
|
|
2999
|
+
* @internal
|
|
3000
|
+
* Handles an incoming ping, by sending a pong back.
|
|
3001
|
+
*/
|
|
3002
|
+
// eslint-disable-next-line @typescript-eslint/require-await
|
|
3003
|
+
async handlePing(sessionKey, ctx) {
|
|
3004
|
+
const session = this.sessions.get(sessionKey);
|
|
3005
|
+
if (session === void 0) {
|
|
3006
|
+
this.logger.withContext({ sessionKey }).warn("[probe] in handlePing, no such session exists");
|
|
3007
|
+
return;
|
|
3008
|
+
}
|
|
3009
|
+
const sent = session.sendPong();
|
|
3010
|
+
if (sent !== 0) {
|
|
3011
|
+
await _optionalChain([this, 'access', _107 => _107.hooks, 'access', _108 => _108.onDidPong, 'optionalCall', _109 => _109(ctx)]);
|
|
3012
|
+
}
|
|
3013
|
+
}
|
|
3014
|
+
async _processClientMsg_withExclusiveAccess(sessionKey, messages, ctx, defer) {
|
|
3015
|
+
const session = this.sessions.get(sessionKey);
|
|
3016
|
+
if (!session) {
|
|
3017
|
+
this.logger.withContext({ sessionKey }).warn("[probe] in handleClientMsgs, no such session exists");
|
|
3018
|
+
return;
|
|
3019
|
+
}
|
|
3020
|
+
const toFanOut = [];
|
|
3021
|
+
const toReply = [];
|
|
3022
|
+
const replyImmediately = (msg) => void session.send(msg);
|
|
3023
|
+
const scheduleFanOut = (msg) => void toFanOut.push(msg);
|
|
3024
|
+
const scheduleReply = (msg) => void toReply.push(msg);
|
|
3025
|
+
for (const msg of messages) {
|
|
3026
|
+
const isMsgAllowed = this.hooks.isClientMsgAllowed(msg, session);
|
|
3027
|
+
if (isMsgAllowed.allowed) {
|
|
3028
|
+
await this.handleOne(
|
|
3029
|
+
session,
|
|
3030
|
+
msg,
|
|
3031
|
+
replyImmediately,
|
|
3032
|
+
scheduleFanOut,
|
|
3033
|
+
scheduleReply,
|
|
3034
|
+
ctx,
|
|
3035
|
+
defer
|
|
3036
|
+
);
|
|
3037
|
+
} else {
|
|
3038
|
+
if (!session.hasNotifiedClientStorageUpdateError) {
|
|
3039
|
+
toReply.push({
|
|
3040
|
+
type: ServerMsgCode.REJECT_STORAGE_OP,
|
|
3041
|
+
opIds: msg.type === ClientMsgCode.UPDATE_STORAGE ? msg.ops.map((op2) => op2.opId) : [],
|
|
3042
|
+
reason: isMsgAllowed.reason
|
|
3043
|
+
});
|
|
3044
|
+
session.setHasNotifiedClientStorageUpdateError();
|
|
3045
|
+
}
|
|
3046
|
+
}
|
|
3047
|
+
}
|
|
3048
|
+
if (toFanOut.length > 0) {
|
|
3049
|
+
this.sendToOthers(sessionKey, toFanOut, ctx, defer);
|
|
3050
|
+
}
|
|
3051
|
+
if (toReply.length > 0) {
|
|
3052
|
+
session.send(toReply);
|
|
3053
|
+
}
|
|
3054
|
+
}
|
|
3055
|
+
// TODO It's a bit bothering how much duplication there is between this method
|
|
3056
|
+
// and the _processClientMsg_withExclusiveAccess version. A better
|
|
3057
|
+
// abstraction is needed.
|
|
3058
|
+
async _processClientMsgFromBackendSession_withExclusiveAccess(session, messages, ctx, defer) {
|
|
3059
|
+
const toFanOut = [];
|
|
3060
|
+
const toReplyImmediately = [];
|
|
3061
|
+
const toReplyAfter = [];
|
|
3062
|
+
const replyImmediately = (msg) => {
|
|
3063
|
+
if (Array.isArray(msg)) {
|
|
3064
|
+
for (const m of msg) {
|
|
3065
|
+
toReplyImmediately.push(m);
|
|
3066
|
+
}
|
|
3067
|
+
} else {
|
|
3068
|
+
toReplyImmediately.push(msg);
|
|
3069
|
+
}
|
|
3070
|
+
};
|
|
3071
|
+
const scheduleFanOut = (msg) => void toFanOut.push(msg);
|
|
3072
|
+
const scheduleReply = (msg) => void toReplyAfter.push(msg);
|
|
3073
|
+
for (const msg of messages) {
|
|
3074
|
+
await this.handleOne(
|
|
3075
|
+
session,
|
|
3076
|
+
msg,
|
|
3077
|
+
replyImmediately,
|
|
3078
|
+
scheduleFanOut,
|
|
3079
|
+
scheduleReply,
|
|
3080
|
+
ctx,
|
|
3081
|
+
defer
|
|
3082
|
+
);
|
|
3083
|
+
}
|
|
3084
|
+
if (toReplyImmediately.length > 0) {
|
|
3085
|
+
session.send(toReplyImmediately);
|
|
3086
|
+
toReplyImmediately.length = 0;
|
|
3087
|
+
}
|
|
3088
|
+
if (toFanOut.length > 0) {
|
|
3089
|
+
this.sendToOthers("(transient)", toFanOut, ctx, defer);
|
|
3090
|
+
toFanOut.length = 0;
|
|
3091
|
+
}
|
|
3092
|
+
if (toReplyAfter.length > 0) {
|
|
3093
|
+
session.send(toReplyAfter);
|
|
3094
|
+
toReplyAfter.length = 0;
|
|
3095
|
+
}
|
|
3096
|
+
}
|
|
3097
|
+
async handleOne(session, msg, replyImmediately, scheduleFanOut, scheduleReply, ctx, defer) {
|
|
3098
|
+
if (!this.mutex.isLocked()) {
|
|
3099
|
+
throw new Error("Handling messages requires exclusive access");
|
|
3100
|
+
}
|
|
3101
|
+
switch (msg.type) {
|
|
3102
|
+
case ClientMsgCode.UPDATE_PRESENCE: {
|
|
3103
|
+
scheduleFanOut({
|
|
3104
|
+
type: ServerMsgCode.UPDATE_PRESENCE,
|
|
3105
|
+
actor: session.actor,
|
|
3106
|
+
data: msg.data,
|
|
3107
|
+
targetActor: msg.targetActor
|
|
3108
|
+
});
|
|
3109
|
+
break;
|
|
3110
|
+
}
|
|
3111
|
+
case ClientMsgCode.BROADCAST_EVENT: {
|
|
3112
|
+
scheduleFanOut({
|
|
3113
|
+
type: ServerMsgCode.BROADCASTED_EVENT,
|
|
3114
|
+
actor: session.actor,
|
|
3115
|
+
event: msg.event
|
|
3116
|
+
});
|
|
3117
|
+
break;
|
|
3118
|
+
}
|
|
3119
|
+
case ClientMsgCode.FETCH_STORAGE: {
|
|
3120
|
+
if (session.version >= 8 /* V8 */) {
|
|
3121
|
+
if (__privateGet(this, __allowStreaming)) {
|
|
3122
|
+
const NODES_PER_CHUNK = 250;
|
|
3123
|
+
for (const chunk of _itertools.chunked.call(void 0,
|
|
3124
|
+
nodeStreamToCompactNodes(this.storage.loadedDriver.iter_nodes()),
|
|
3125
|
+
NODES_PER_CHUNK
|
|
3126
|
+
)) {
|
|
3127
|
+
replyImmediately({
|
|
3128
|
+
type: ServerMsgCode.STORAGE_CHUNK,
|
|
3129
|
+
nodes: chunk
|
|
3130
|
+
});
|
|
3131
|
+
}
|
|
3132
|
+
} else {
|
|
3133
|
+
replyImmediately({
|
|
3134
|
+
type: ServerMsgCode.STORAGE_CHUNK,
|
|
3135
|
+
nodes: Array.from(
|
|
3136
|
+
nodeStreamToCompactNodes(this.storage.loadedDriver.iter_nodes())
|
|
3137
|
+
)
|
|
3138
|
+
});
|
|
3139
|
+
}
|
|
3140
|
+
replyImmediately({ type: ServerMsgCode.STORAGE_STREAM_END });
|
|
3141
|
+
} else {
|
|
3142
|
+
replyImmediately({
|
|
3143
|
+
type: ServerMsgCode.STORAGE_STATE_V7,
|
|
3144
|
+
items: Array.from(this.storage.loadedDriver.iter_nodes())
|
|
3145
|
+
});
|
|
3146
|
+
}
|
|
3147
|
+
break;
|
|
3148
|
+
}
|
|
3149
|
+
case ClientMsgCode.UPDATE_STORAGE: {
|
|
3150
|
+
_optionalChain([this, 'access', _110 => _110.driver, 'access', _111 => _111.bump_storage_version, 'optionalCall', _112 => _112()]);
|
|
3151
|
+
const result = await this.storage.applyOps(msg.ops);
|
|
3152
|
+
const opsToForward = result.flatMap(
|
|
3153
|
+
(r) => r.action === "accepted" ? [r.op] : []
|
|
3154
|
+
);
|
|
3155
|
+
const opsToSendBack = result.flatMap((r) => {
|
|
3156
|
+
switch (r.action) {
|
|
3157
|
+
case "ignored":
|
|
3158
|
+
return r.ignoredOpId !== void 0 ? [ackIgnoredOp(r.ignoredOpId)] : [];
|
|
3159
|
+
case "accepted":
|
|
3160
|
+
return r.fix !== void 0 ? [r.fix] : [];
|
|
3161
|
+
default:
|
|
3162
|
+
return assertNever(r, "Unhandled case");
|
|
3163
|
+
}
|
|
3164
|
+
});
|
|
3165
|
+
if (opsToForward.length > 0) {
|
|
3166
|
+
scheduleFanOut({
|
|
3167
|
+
type: ServerMsgCode.UPDATE_STORAGE,
|
|
3168
|
+
ops: opsToForward.map(stripOpId)
|
|
3169
|
+
});
|
|
3170
|
+
scheduleReply({
|
|
3171
|
+
type: ServerMsgCode.UPDATE_STORAGE,
|
|
3172
|
+
ops: opsToForward
|
|
3173
|
+
});
|
|
3174
|
+
}
|
|
3175
|
+
if (opsToSendBack.length > 0) {
|
|
3176
|
+
replyImmediately({
|
|
3177
|
+
type: ServerMsgCode.UPDATE_STORAGE,
|
|
3178
|
+
ops: opsToSendBack
|
|
3179
|
+
});
|
|
3180
|
+
}
|
|
3181
|
+
if (opsToForward.length > 0) {
|
|
3182
|
+
const p$ = _optionalChain([this, 'access', _113 => _113.hooks, 'access', _114 => _114.postClientMsgStorageDidUpdate, 'optionalCall', _115 => _115(ctx)]);
|
|
3183
|
+
if (p$) defer(p$);
|
|
3184
|
+
}
|
|
3185
|
+
break;
|
|
3186
|
+
}
|
|
3187
|
+
case ClientMsgCode.FETCH_YDOC: {
|
|
3188
|
+
const vector = msg.vector;
|
|
3189
|
+
const guid = msg.guid;
|
|
3190
|
+
const isV2 = msg.v2;
|
|
3191
|
+
const [update, stateVector, snapshotHash] = await Promise.all([
|
|
3192
|
+
this.yjsStorage.getYDocUpdate(this.logger, vector, guid, isV2),
|
|
3193
|
+
this.yjsStorage.getYStateVector(guid),
|
|
3194
|
+
this.yjsStorage.getSnapshotHash({ guid, isV2 })
|
|
3195
|
+
]);
|
|
3196
|
+
if (update !== null && snapshotHash !== null) {
|
|
3197
|
+
replyImmediately({
|
|
3198
|
+
type: ServerMsgCode.UPDATE_YDOC,
|
|
3199
|
+
update,
|
|
3200
|
+
isSync: true,
|
|
3201
|
+
// this is no longer used by the client, instead we use the presence of stateVector
|
|
3202
|
+
stateVector,
|
|
3203
|
+
guid,
|
|
3204
|
+
v2: isV2,
|
|
3205
|
+
remoteSnapshotHash: snapshotHash
|
|
3206
|
+
});
|
|
3207
|
+
}
|
|
3208
|
+
break;
|
|
3209
|
+
}
|
|
3210
|
+
case ClientMsgCode.UPDATE_YDOC: {
|
|
3211
|
+
const update = msg.update;
|
|
3212
|
+
const guid = msg.guid;
|
|
3213
|
+
const isV2 = msg.v2;
|
|
3214
|
+
const [result, error3] = await tryCatch(
|
|
3215
|
+
this.yjsStorage.addYDocUpdate(this.logger, update, guid, isV2)
|
|
3216
|
+
);
|
|
3217
|
+
if (error3)
|
|
3218
|
+
break;
|
|
3219
|
+
this.sendToAll(
|
|
3220
|
+
{
|
|
3221
|
+
type: ServerMsgCode.UPDATE_YDOC,
|
|
3222
|
+
update,
|
|
3223
|
+
guid,
|
|
3224
|
+
isSync: false,
|
|
3225
|
+
stateVector: null,
|
|
3226
|
+
v2: isV2,
|
|
3227
|
+
remoteSnapshotHash: result.snapshotHash
|
|
3228
|
+
},
|
|
3229
|
+
ctx,
|
|
3230
|
+
defer
|
|
3231
|
+
);
|
|
3232
|
+
if (result.isUpdated) {
|
|
3233
|
+
const p$ = _optionalChain([this, 'access', _116 => _116.hooks, 'access', _117 => _117.postClientMsgYdocDidUpdate, 'optionalCall', _118 => _118(ctx, session)]);
|
|
3234
|
+
if (p$) defer(p$);
|
|
3235
|
+
}
|
|
3236
|
+
break;
|
|
3237
|
+
}
|
|
3238
|
+
default: {
|
|
3239
|
+
try {
|
|
3240
|
+
return assertNever(msg, "Unrecognized client msg");
|
|
3241
|
+
} catch (e2) {
|
|
3242
|
+
}
|
|
3243
|
+
}
|
|
3244
|
+
}
|
|
3245
|
+
}
|
|
3246
|
+
};
|
|
3247
|
+
__debug2 = new WeakMap();
|
|
3248
|
+
__allowStreaming = new WeakMap();
|
|
3249
|
+
|
|
3250
|
+
|
|
3251
|
+
|
|
3252
|
+
|
|
3253
|
+
|
|
3254
|
+
|
|
3255
|
+
|
|
3256
|
+
|
|
3257
|
+
|
|
3258
|
+
|
|
3259
|
+
|
|
3260
|
+
|
|
3261
|
+
|
|
3262
|
+
|
|
3263
|
+
|
|
3264
|
+
|
|
3265
|
+
|
|
3266
|
+
|
|
3267
|
+
|
|
3268
|
+
|
|
3269
|
+
|
|
3270
|
+
|
|
3271
|
+
|
|
3272
|
+
|
|
3273
|
+
|
|
3274
|
+
|
|
3275
|
+
|
|
3276
|
+
|
|
3277
|
+
|
|
3278
|
+
|
|
3279
|
+
|
|
3280
|
+
|
|
3281
|
+
|
|
3282
|
+
|
|
3283
|
+
|
|
3284
|
+
exports.BackendSession = BackendSession; exports.BrowserSession = BrowserSession; exports.ConsoleTarget = ConsoleTarget; exports.DefaultMap = DefaultMap2; exports.InMemoryDriver = InMemoryDriver; exports.LogLevel = LogLevel; exports.LogTarget = LogTarget; exports.Logger = Logger; exports.NestedMap = NestedMap; exports.ProtocolVersion = ProtocolVersion; exports.ROOT_YDOC_ID = ROOT_YDOC_ID; exports.Room = Room; exports.UniqueMap = UniqueMap; exports.ackIgnoredOp = ackIgnoredOp; exports.clientMsgDecoder = clientMsgDecoder; exports.concatUint8Arrays = concatUint8Arrays; exports.guidDecoder = guidDecoder; exports.jsonObjectYolo = jsonObjectYolo; exports.jsonYolo = jsonYolo; exports.makeInMemorySnapshot = makeInMemorySnapshot; exports.makeMetadataDB = makeMetadataDB; exports.plainLsonToNodeStream = plainLsonToNodeStream; exports.protocolVersionDecoder = protocolVersionDecoder; exports.quote = quote; exports.serializeServerMsg = serialize; exports.snapshotToLossyJson_eager = snapshotToLossyJson_eager; exports.snapshotToLossyJson_lazy = snapshotToLossyJson_lazy; exports.snapshotToNodeStream = snapshotToNodeStream; exports.snapshotToPlainLson_eager = snapshotToPlainLson_eager; exports.snapshotToPlainLson_lazy = snapshotToPlainLson_lazy; exports.test_only__Storage = Storage; exports.test_only__YjsStorage = YjsStorage; exports.transientClientMsgDecoder = transientClientMsgDecoder; exports.tryCatch = tryCatch;
|
|
3285
|
+
//# sourceMappingURL=index.cjs.map
|