@arbidocs/coedit 0.0.0-stage → 0.4.33

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.
@@ -0,0 +1,1723 @@
1
+ 'use strict';
2
+
3
+ var Y3 = require('yjs');
4
+ var awareness = require('y-protocols/awareness');
5
+ var provider = require('@hocuspocus/provider');
6
+ var store = require('@docx-editor.dev/core/store');
7
+ var collaboration = require('@docx-editor.dev/core/collaboration');
8
+ var replication = require('@docx-editor.dev/core/collaboration/replication');
9
+
10
+ function _interopNamespace(e) {
11
+ if (e && e.__esModule) return e;
12
+ var n = Object.create(null);
13
+ if (e) {
14
+ Object.keys(e).forEach(function (k) {
15
+ if (k !== 'default') {
16
+ var d = Object.getOwnPropertyDescriptor(e, k);
17
+ Object.defineProperty(n, k, d.get ? d : {
18
+ enumerable: true,
19
+ get: function () { return e[k]; }
20
+ });
21
+ }
22
+ });
23
+ }
24
+ n.default = e;
25
+ return Object.freeze(n);
26
+ }
27
+
28
+ var Y3__namespace = /*#__PURE__*/_interopNamespace(Y3);
29
+
30
+ // src/hocuspocus.ts
31
+
32
+ // src/rich.ts
33
+ var WML = "http://schemas.openxmlformats.org/wordprocessingml/2006/main";
34
+ var WRAPPERS = /* @__PURE__ */ new Set([
35
+ "ins",
36
+ "del",
37
+ "moveFrom",
38
+ "moveTo",
39
+ "hyperlink",
40
+ "smartTag",
41
+ "fldSimple",
42
+ "customXml",
43
+ "dir",
44
+ "bdo",
45
+ "sdtContent"
46
+ ]);
47
+ var TEXT_ELEMENTS = /* @__PURE__ */ new Set(["t", "delText", "instrText"]);
48
+ var isEl = (n) => typeof n !== "string";
49
+ var isWml = (el, local) => el.ns === WML && el.local === local;
50
+ function describe(el) {
51
+ const desc = { ns: el.ns, local: el.local, attrs: el.attrs, bindings: el.bindings };
52
+ if (el.prefix !== void 0) desc.prefix = el.prefix;
53
+ return desc;
54
+ }
55
+ function withChildren(desc, children, id) {
56
+ const el = {
57
+ ns: desc.ns,
58
+ local: desc.local,
59
+ attrs: desc.attrs,
60
+ bindings: desc.bindings,
61
+ children
62
+ };
63
+ if (desc.prefix !== void 0) el.prefix = desc.prefix;
64
+ if (id !== void 0) el._id = id;
65
+ return el;
66
+ }
67
+ var counter = 0;
68
+ function newRunId() {
69
+ counter += 1;
70
+ return `${Math.random().toString(36).slice(2, 8)}${counter.toString(36)}`;
71
+ }
72
+ var Unrepresentable = class extends Error {
73
+ };
74
+ function fail() {
75
+ throw new Unrepresentable();
76
+ }
77
+ function encodeRun(run, wrappers, out) {
78
+ const children = run.children;
79
+ let rPr = null;
80
+ let start = 0;
81
+ const first = children[0];
82
+ if (first !== void 0 && isEl(first) && isWml(first, "rPr")) {
83
+ rPr = first;
84
+ start = 1;
85
+ }
86
+ const base = { r: rPr, ra: describe(run), ri: run._id ?? newRunId(), w: wrappers };
87
+ let produced = false;
88
+ for (let i = start; i < children.length; i++) {
89
+ const child = children[i];
90
+ if (!isEl(child)) fail();
91
+ const only = child.children[0];
92
+ if (child.ns === WML && TEXT_ELEMENTS.has(child.local) && child.children.length === 1 && typeof only === "string" && only.length > 0) {
93
+ const tx = describe(child);
94
+ for (let c = 0; c < only.length; c++) out.push({ v: only[c], a: { ...base, tx, k: null } });
95
+ } else {
96
+ out.push({ v: { x: child }, a: { ...base, tx: null, k: null } });
97
+ }
98
+ produced = true;
99
+ }
100
+ if (!produced)
101
+ out.push({
102
+ v: { x: { ns: WML, local: "e", attrs: [], bindings: [], children: [] } },
103
+ a: { ...base, tx: null, k: "e" }
104
+ });
105
+ }
106
+ function encodeInline(children, wrappers, out) {
107
+ for (const child of children) {
108
+ if (!isEl(child)) fail();
109
+ if (child.ns === WML && child.local === "r") {
110
+ encodeRun(child, wrappers, out);
111
+ } else if (child.ns === WML && WRAPPERS.has(child.local)) {
112
+ const before = out.length;
113
+ const desc = { ...describe(child), head: [], id: child._id ?? newRunId() };
114
+ let ok = true;
115
+ const inner = [];
116
+ try {
117
+ encodeInline(child.children, [...wrappers, desc], inner);
118
+ } catch (error) {
119
+ if (!(error instanceof Unrepresentable)) throw error;
120
+ ok = false;
121
+ }
122
+ if (ok && inner.length > 0) out.push(...inner);
123
+ else {
124
+ out.length = before;
125
+ out.push(atom(child, wrappers));
126
+ }
127
+ } else if (child.ns === WML && child.local === "sdt") {
128
+ const last = child.children[child.children.length - 1];
129
+ const head = child.children.slice(0, -1);
130
+ if (last !== void 0 && isEl(last) && isWml(last, "sdtContent") && head.every(isEl)) {
131
+ const before = out.length;
132
+ const sdt = {
133
+ ...describe(child),
134
+ head,
135
+ id: child._id ?? newRunId()
136
+ };
137
+ const content = { ...describe(last), head: [], id: last._id ?? newRunId() };
138
+ const inner = [];
139
+ let ok = true;
140
+ try {
141
+ encodeInline(last.children, [...wrappers, sdt, content], inner);
142
+ } catch (error) {
143
+ if (!(error instanceof Unrepresentable)) throw error;
144
+ ok = false;
145
+ }
146
+ if (ok && inner.length > 0) out.push(...inner);
147
+ else {
148
+ out.length = before;
149
+ out.push(atom(child, wrappers));
150
+ }
151
+ } else out.push(atom(child, wrappers));
152
+ } else {
153
+ out.push(atom(child, wrappers));
154
+ }
155
+ }
156
+ }
157
+ function atom(el, wrappers) {
158
+ return { v: { x: el }, a: { r: null, ra: null, ri: null, w: wrappers, tx: null, k: "a" } };
159
+ }
160
+ function encodeParagraph(children) {
161
+ const head = [];
162
+ let start = 0;
163
+ const first = children[0];
164
+ if (first !== void 0 && isEl(first) && isWml(first, "pPr")) {
165
+ head.push(first);
166
+ start = 1;
167
+ }
168
+ const units = [];
169
+ try {
170
+ encodeInline(children.slice(start), [], units);
171
+ } catch (error) {
172
+ if (error instanceof Unrepresentable) return null;
173
+ throw error;
174
+ }
175
+ return { head, units };
176
+ }
177
+ var same = (a, b) => JSON.stringify(a) === JSON.stringify(b);
178
+ function decodeRange(units, from, to, depth) {
179
+ const out = [];
180
+ let i = from;
181
+ while (i < to) {
182
+ const unit = units[i];
183
+ const { w } = unit.a;
184
+ if (w.length > depth) {
185
+ const wrapper = w[depth];
186
+ let j = i + 1;
187
+ while (j < to && units[j].a.w.length > depth && same(units[j].a.w[depth], wrapper)) j++;
188
+ out.push(
189
+ withChildren(wrapper, [...wrapper.head, ...decodeRange(units, i, j, depth + 1)], wrapper.id)
190
+ );
191
+ i = j;
192
+ } else if (unit.a.k === "a") {
193
+ out.push(unit.v.x);
194
+ i++;
195
+ } else {
196
+ let j = i + 1;
197
+ while (unit.a.k !== "e" && j < to && units[j].a.w.length === depth && units[j].a.k !== "a" && units[j].a.k !== "e" && units[j].a.ri === unit.a.ri && same(units[j].a.r, unit.a.r) && same(units[j].a.ra, unit.a.ra)) {
198
+ j++;
199
+ }
200
+ out.push(decodeRun(units, i, j));
201
+ i = j;
202
+ }
203
+ }
204
+ return out;
205
+ }
206
+ function decodeRun(units, from, to) {
207
+ const { r, ra } = units[from].a;
208
+ const children = [];
209
+ if (r) children.push(r);
210
+ let i = from;
211
+ while (i < to) {
212
+ const unit = units[i];
213
+ if (unit.a.k === "e") {
214
+ i++;
215
+ } else if (typeof unit.v === "string") {
216
+ let j = i + 1;
217
+ while (j < to && typeof units[j].v === "string" && same(units[j].a.tx, unit.a.tx)) j++;
218
+ let text = "";
219
+ for (let c = i; c < j; c++) text += units[c].v;
220
+ children.push(withChildren(unit.a.tx, [text]));
221
+ i = j;
222
+ } else {
223
+ children.push(unit.v.x);
224
+ i++;
225
+ }
226
+ }
227
+ return withChildren(ra, children, units[from].a.ri ?? void 0);
228
+ }
229
+ function decodeInline(units) {
230
+ return decodeRange(units, 0, units.length, 0);
231
+ }
232
+ var KEYS = ["r", "ra", "ri", "w", "tx", "k"];
233
+ function toAttributes(a) {
234
+ const out = {};
235
+ for (const key of KEYS) {
236
+ const value = a[key];
237
+ if (value !== null && !(key === "w" && value.length === 0)) out[key] = value;
238
+ }
239
+ return out;
240
+ }
241
+ function fromAttributes(attributes) {
242
+ const a = attributes ?? {};
243
+ return {
244
+ r: a.r ?? null,
245
+ ra: a.ra ?? null,
246
+ ri: a.ri ?? null,
247
+ w: a.w ?? [],
248
+ tx: a.tx ?? null,
249
+ k: a.k ?? null
250
+ };
251
+ }
252
+ function readUnits(text) {
253
+ const units = [];
254
+ for (const op of text.toDelta()) {
255
+ const a = fromAttributes(op.attributes);
256
+ if (typeof op.insert === "string") {
257
+ for (let i = 0; i < op.insert.length; i++) units.push({ v: op.insert[i], a });
258
+ } else {
259
+ units.push({ v: op.insert, a });
260
+ }
261
+ }
262
+ return units;
263
+ }
264
+ var valueKeyOf = (u) => typeof u.v === "string" ? u.v : JSON.stringify(u.v);
265
+ var attrsKeyOf = (u) => JSON.stringify(u.a);
266
+ function attributeChange(from, to) {
267
+ const a = toAttributes(from);
268
+ const b = toAttributes(to);
269
+ const change = {};
270
+ for (const key of KEYS) {
271
+ if (JSON.stringify(a[key]) !== JSON.stringify(b[key])) change[key] = b[key] ?? null;
272
+ }
273
+ return change;
274
+ }
275
+ function pushRetain(delta, length, attributes) {
276
+ if (length === 0) return;
277
+ const last = delta[delta.length - 1];
278
+ const empty = !attributes || Object.keys(attributes).length === 0;
279
+ if (last && last.retain !== void 0 && !last.attributes && empty) {
280
+ last.retain += length;
281
+ return;
282
+ }
283
+ delta.push(empty ? { retain: length } : { retain: length, attributes });
284
+ }
285
+ function reformat(delta, previous, from, next, to, length) {
286
+ let i = 0;
287
+ while (i < length) {
288
+ const change = attributeChange(previous[from + i].a, next[to + i].a);
289
+ const key = JSON.stringify(change);
290
+ let j = i + 1;
291
+ while (j < length && JSON.stringify(attributeChange(previous[from + j].a, next[to + j].a)) === key) {
292
+ j++;
293
+ }
294
+ pushRetain(delta, j - i, change);
295
+ i = j;
296
+ }
297
+ }
298
+ function applyUnits(text, previous, next) {
299
+ let start = 0;
300
+ const limit = Math.min(previous.length, next.length);
301
+ while (start < limit && valueKeyOf(previous[start]) === valueKeyOf(next[start])) start++;
302
+ let endPrev = previous.length;
303
+ let endNext = next.length;
304
+ while (endPrev > start && endNext > start && valueKeyOf(previous[endPrev - 1]) === valueKeyOf(next[endNext - 1])) {
305
+ endPrev--;
306
+ endNext--;
307
+ }
308
+ const delta = [];
309
+ reformat(delta, previous, 0, next, 0, start);
310
+ if (endPrev > start) delta.push({ delete: endPrev - start });
311
+ let i = start;
312
+ while (i < endNext) {
313
+ const unit = next[i];
314
+ if (typeof unit.v !== "string") {
315
+ delta.push({ insert: unit.v, attributes: toAttributes(unit.a) });
316
+ i++;
317
+ continue;
318
+ }
319
+ let j = i + 1;
320
+ let chunk = unit.v;
321
+ const attributes = attrsKeyOf(unit);
322
+ while (j < endNext && typeof next[j].v === "string" && attrsKeyOf(next[j]) === attributes) {
323
+ chunk += next[j].v;
324
+ j++;
325
+ }
326
+ delta.push({ insert: chunk, attributes: toAttributes(unit.a) });
327
+ i = j;
328
+ }
329
+ reformat(delta, previous, endPrev, next, endNext, previous.length - endPrev);
330
+ if (delta.every((op) => op.retain !== void 0 && !op.attributes)) return;
331
+ text.applyDelta(delta, { sanitize: false });
332
+ }
333
+ function writeUnits(text, units) {
334
+ applyUnits(text, [], units);
335
+ }
336
+
337
+ // src/mirror.ts
338
+ function isRichParagraph(map) {
339
+ return map.has("inline");
340
+ }
341
+ function isParagraphEl(el) {
342
+ return el.ns === WML && el.local === "p";
343
+ }
344
+ function fieldsOf(map, xml) {
345
+ map.set("ns", xml.ns);
346
+ map.set("local", xml.local);
347
+ if (xml.prefix !== void 0) map.set("prefix", xml.prefix);
348
+ const attrs = new Y3__namespace.Array();
349
+ attrs.push(xml.attrs.map((a) => ({ ...a })));
350
+ map.set("attrs", attrs);
351
+ const bindings = new Y3__namespace.Array();
352
+ bindings.push(xml.bindings.map((b) => ({ ...b })));
353
+ map.set("bindings", bindings);
354
+ }
355
+ function newText(value) {
356
+ const text = new Y3__namespace.Text();
357
+ text.insert(0, value);
358
+ return text;
359
+ }
360
+ function createRichParagraph(xml, encoded) {
361
+ const map = new Y3__namespace.Map();
362
+ fieldsOf(map, xml);
363
+ const children = new Y3__namespace.Array();
364
+ children.push(encoded.head.map((el) => xmlToLive(el)));
365
+ map.set("children", children);
366
+ const inline = new Y3__namespace.Text();
367
+ writeUnits(inline, encoded.units);
368
+ map.set("inline", inline);
369
+ return map;
370
+ }
371
+ function xmlToLive(xml) {
372
+ if (isParagraphEl(xml)) {
373
+ const encoded = encodeParagraph(xml.children);
374
+ if (encoded) return createRichParagraph(xml, encoded);
375
+ }
376
+ return xmlToGenericLive(xml);
377
+ }
378
+ function xmlToGenericLive(xml) {
379
+ const map = new Y3__namespace.Map();
380
+ fieldsOf(map, xml);
381
+ const children = new Y3__namespace.Array();
382
+ children.push(xml.children.map((c) => typeof c === "string" ? newText(c) : xmlToLive(c)));
383
+ map.set("children", children);
384
+ return map;
385
+ }
386
+ function liveToXml(map) {
387
+ const prefix = map.get("prefix");
388
+ const stored = map.get("children").toArray().map((child) => child instanceof Y3__namespace.Text ? child.toString() : liveToXml(child));
389
+ const children = isRichParagraph(map) ? [...stored, ...decodeInline(readUnits(map.get("inline")))] : stored;
390
+ const xml = {
391
+ ns: map.get("ns"),
392
+ local: map.get("local"),
393
+ attrs: map.get("attrs").toArray().map((a) => ({ ...a })),
394
+ bindings: map.get("bindings").toArray().map((b) => ({ ...b })),
395
+ children
396
+ };
397
+ if (prefix !== void 0) xml.prefix = prefix;
398
+ return xml;
399
+ }
400
+ var escapeText = (value) => value.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;");
401
+ var escapeAttr = (value) => value.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;").replace(/"/g, "&quot;").replace(/\t/g, "&#9;").replace(/\n/g, "&#10;").replace(/\r/g, "&#13;");
402
+ var qualified = (prefix, local) => prefix ? `${prefix}:${local}` : local;
403
+ function serializeXml(el, scope) {
404
+ const inScope = new Map(scope);
405
+ for (const b of el.bindings) inScope.set(b.prefix, b.ns);
406
+ let out = `<${qualified(el.prefix, el.local)}`;
407
+ for (const b of el.bindings) {
408
+ out += b.prefix === "" ? ` xmlns="${escapeAttr(b.ns)}"` : ` xmlns:${b.prefix}="${escapeAttr(b.ns)}"`;
409
+ }
410
+ for (const a of el.attrs) {
411
+ let p = a.prefix;
412
+ if (a.ns !== "" && !p) {
413
+ for (const [candidate, uri] of inScope) {
414
+ if (candidate !== "" && uri === a.ns) {
415
+ p = candidate;
416
+ break;
417
+ }
418
+ }
419
+ }
420
+ out += ` ${qualified(p, a.local)}="${escapeAttr(a.value)}"`;
421
+ }
422
+ if (el.children.length === 0) return `${out}/>`;
423
+ out += ">";
424
+ for (const child of el.children) {
425
+ out += typeof child === "string" ? escapeText(child) : serializeXml(child, inScope);
426
+ }
427
+ return `${out}</${qualified(el.prefix, el.local)}>`;
428
+ }
429
+ function serializeLive(el, scope) {
430
+ if (isRichParagraph(el)) return serializeXml(liveToXml(el), scope);
431
+ const prefix = el.get("prefix");
432
+ const local = el.get("local");
433
+ const bindings = el.get("bindings").toArray();
434
+ const attrs = el.get("attrs").toArray();
435
+ const children = el.get("children").toArray();
436
+ const inScope = new Map(scope);
437
+ for (const b of bindings) inScope.set(b.prefix, b.ns);
438
+ let out = `<${qualified(prefix, local)}`;
439
+ for (const b of bindings) {
440
+ out += b.prefix === "" ? ` xmlns="${escapeAttr(b.ns)}"` : ` xmlns:${b.prefix}="${escapeAttr(b.ns)}"`;
441
+ }
442
+ for (const a of attrs) {
443
+ let p = a.prefix;
444
+ if (a.ns !== "" && !p) {
445
+ for (const [candidate, uri] of inScope) {
446
+ if (candidate !== "" && uri === a.ns) {
447
+ p = candidate;
448
+ break;
449
+ }
450
+ }
451
+ }
452
+ out += ` ${qualified(p, a.local)}="${escapeAttr(a.value)}"`;
453
+ }
454
+ if (children.length === 0) return `${out}/>`;
455
+ out += ">";
456
+ for (const child of children) {
457
+ out += child instanceof Y3__namespace.Text ? escapeText(child.toString()) : serializeLive(child, inScope);
458
+ }
459
+ return `${out}</${qualified(prefix, local)}>`;
460
+ }
461
+
462
+ // src/encoding.ts
463
+ var COLLAB_FORMAT = "arbi-docx/1";
464
+ function isBootstrapped(doc) {
465
+ return doc.getMap("meta").get("format") !== void 0;
466
+ }
467
+ function readFormat(doc) {
468
+ const format = doc.getMap("meta").get("format");
469
+ return typeof format === "string" ? format : void 0;
470
+ }
471
+ function serializePart(doc, partName) {
472
+ const root = doc.getMap("parts").get(partName);
473
+ if (!root) throw new Error(`collab: part ${partName} is not in the room`);
474
+ return `<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
475
+ ${serializeLive(root, /* @__PURE__ */ new Map())}`;
476
+ }
477
+ function contentTypeOf(doc, partName) {
478
+ const override = doc.getMap("ctOverrides").get(partName.toLowerCase());
479
+ if (override !== void 0) return override;
480
+ const dot = partName.lastIndexOf(".");
481
+ if (dot < 0) return void 0;
482
+ return doc.getMap("ctDefaults").get(partName.slice(dot + 1).toLowerCase());
483
+ }
484
+ function registerMirror(doc, registry, only) {
485
+ const parts = doc.getMap("parts");
486
+ if (!only) {
487
+ registry.clear();
488
+ for (const [name, root] of parts) registry.registerTree(name, root);
489
+ return;
490
+ }
491
+ for (const name of only) {
492
+ registry.dropPart(name);
493
+ const root = parts.get(name);
494
+ if (root) registry.registerTree(name, root);
495
+ }
496
+ }
497
+ var MaterializeCache = class {
498
+ parts = /* @__PURE__ */ new Map();
499
+ dirty = /* @__PURE__ */ new Set();
500
+ everything = true;
501
+ stop = [];
502
+ constructor(doc) {
503
+ const parts = doc.getMap("parts");
504
+ const onParts = (events) => {
505
+ for (const event of events) {
506
+ if (event.target === parts) {
507
+ for (const key of event.keysChanged) this.dirty.add(key);
508
+ } else if (typeof event.path[0] === "string") this.dirty.add(event.path[0]);
509
+ }
510
+ };
511
+ parts.observeDeep(onParts);
512
+ this.stop.push(() => parts.unobserveDeep(onParts));
513
+ for (const name of ["ctOverrides", "ctDefaults"]) {
514
+ const map = doc.getMap(name);
515
+ const onTypes = () => {
516
+ this.everything = true;
517
+ };
518
+ map.observe(onTypes);
519
+ this.stop.push(() => map.unobserve(onTypes));
520
+ }
521
+ }
522
+ /** Names to re-read, or null when everything must be. Clears the pending set. */
523
+ take() {
524
+ const names = this.everything ? null : this.dirty;
525
+ this.dirty = /* @__PURE__ */ new Set();
526
+ this.everything = false;
527
+ return names;
528
+ }
529
+ /** Mark parts changed again, for a materialisation that did not complete. */
530
+ restore(names) {
531
+ if (names === null) this.everything = true;
532
+ else for (const name of names) this.dirty.add(name);
533
+ }
534
+ destroy() {
535
+ for (const stop of this.stop.splice(0)) stop();
536
+ }
537
+ };
538
+ var RELS_CONTENT_TYPE = "application/vnd.openxmlformats-package.relationships+xml";
539
+ var RELS_NAMESPACE = "http://schemas.openxmlformats.org/package/2006/relationships";
540
+ function isRelsPart(partName) {
541
+ return partName.endsWith(".rels");
542
+ }
543
+ function relationshipsXml(records) {
544
+ const rows = records.map(
545
+ (r) => `<Relationship Id="${escapeAttr(r.id)}" Type="${escapeAttr(r.type)}" Target="${escapeAttr(r.rawTarget)}"${r.targetMode === "External" ? ' TargetMode="External"' : ""}/>`
546
+ ).join("");
547
+ return `<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
548
+ <Relationships xmlns="${RELS_NAMESPACE}">${rows}</Relationships>`;
549
+ }
550
+ var MaterializeError = class extends Error {
551
+ constructor(message) {
552
+ super(message);
553
+ this.name = "MaterializeError";
554
+ }
555
+ };
556
+ function materialize(doc, cache, changed = null) {
557
+ const meta = doc.getMap("meta");
558
+ const main = meta.get("mainDocumentPart");
559
+ if (typeof main !== "string") throw new MaterializeError("room has no main document part");
560
+ const parts = /* @__PURE__ */ new Map();
561
+ for (const name of doc.getMap("parts").keys()) {
562
+ if (isRelsPart(name)) continue;
563
+ const reusable = cache && changed && !changed.has(name) ? cache.parts.get(name) : void 0;
564
+ if (reusable) {
565
+ parts.set(name, reusable);
566
+ continue;
567
+ }
568
+ const contentType = contentTypeOf(doc, name);
569
+ if (contentType === void 0) throw new MaterializeError(`part ${name} has no content type`);
570
+ const read = store.readOoxmlPart(serializePart(doc, name), { name, contentType });
571
+ if (!read.ok) throw new MaterializeError(`part ${name} is not readable: ${read.reason}`);
572
+ parts.set(name, read.part);
573
+ }
574
+ if (cache) {
575
+ cache.parts.clear();
576
+ for (const [name, part] of parts) if (!isRelsPart(name)) cache.parts.set(name, part);
577
+ }
578
+ const partBytes = /* @__PURE__ */ new Map();
579
+ for (const [name, bytes] of doc.getMap("binaries")) partBytes.set(name, bytes);
580
+ const relationships = /* @__PURE__ */ new Map();
581
+ const externalTargets = [];
582
+ for (const [owner, byId] of doc.getMap("rels")) {
583
+ const records = [...byId.values()].sort((a, b) => a.order - b.order || a.id.localeCompare(b.id));
584
+ if (records.length === 0) continue;
585
+ const internal = records.filter((r) => r.targetMode !== "External");
586
+ if (internal.length > 0) relationships.set(owner, internal);
587
+ for (const r of records) {
588
+ if (r.targetMode !== "External") continue;
589
+ externalTargets.push({
590
+ ownerPart: r.ownerPart,
591
+ id: r.id,
592
+ type: r.type,
593
+ rawTarget: r.rawTarget,
594
+ sinkSafe: store.validateExternalTarget(r.rawTarget).ok
595
+ });
596
+ }
597
+ const relsName = store.relsPartNameFor(owner);
598
+ const read = store.readOoxmlPart(relationshipsXml(records), {
599
+ name: relsName,
600
+ contentType: RELS_CONTENT_TYPE
601
+ });
602
+ if (!read.ok)
603
+ throw new MaterializeError(`relationships of ${owner} are not valid: ${read.reason}`);
604
+ parts.set(relsName, read.part);
605
+ }
606
+ const contentTypes = {
607
+ defaults: new Map(doc.getMap("ctDefaults")),
608
+ overrides: new Map(doc.getMap("ctOverrides"))
609
+ };
610
+ return {
611
+ parts,
612
+ partBytes,
613
+ relationships,
614
+ externalTargets,
615
+ contentTypes,
616
+ mainDocumentPart: main
617
+ };
618
+ }
619
+ function materializeBytes(doc) {
620
+ return store.writeOoxmlPackage(materialize(doc));
621
+ }
622
+ var RegistryError = class extends Error {
623
+ constructor(message) {
624
+ super(message);
625
+ this.name = "RegistryError";
626
+ }
627
+ };
628
+ function childrenOf(el) {
629
+ return el.get("children");
630
+ }
631
+ function attrsOf(el) {
632
+ return el.get("attrs");
633
+ }
634
+ function bindingsOf(el) {
635
+ return el.get("bindings");
636
+ }
637
+ var IDENTIFIED = /* @__PURE__ */ new Set([
638
+ "r",
639
+ "ins",
640
+ "del",
641
+ "moveFrom",
642
+ "moveTo",
643
+ "hyperlink",
644
+ "smartTag",
645
+ "fldSimple",
646
+ "customXml",
647
+ "dir",
648
+ "bdo",
649
+ "sdt",
650
+ "sdtContent"
651
+ ]);
652
+ function plainToXml(node) {
653
+ if (node.type === "text") return node.value;
654
+ const xml = {
655
+ ns: node.ns,
656
+ local: node.local,
657
+ attrs: node.attrs.map((a) => ({ ...a })),
658
+ bindings: node.bindings.map((b) => ({ ...b })),
659
+ children: node.children.map(plainToXml)
660
+ };
661
+ if (node.prefix !== void 0) xml.prefix = node.prefix;
662
+ if (node.ns === WML && IDENTIFIED.has(node.local)) {
663
+ node.rid ??= newRunId();
664
+ xml._id = node.rid;
665
+ }
666
+ return xml;
667
+ }
668
+ function xmlToPlain(xml, id) {
669
+ if (typeof xml === "string") return { type: "text", id, value: xml };
670
+ const plain = {
671
+ type: "element",
672
+ id,
673
+ ns: xml.ns,
674
+ local: xml.local,
675
+ attrs: xml.attrs.map((a) => ({ ...a })),
676
+ bindings: xml.bindings.map((b) => ({ ...b })),
677
+ children: xml.children.map((child, index) => xmlToPlain(child, `${id}.${index}`))
678
+ };
679
+ if (xml.prefix !== void 0) plain.prefix = xml.prefix;
680
+ if (xml._id !== void 0) plain.rid = xml._id;
681
+ return plain;
682
+ }
683
+ var partOf = (id) => id.slice(0, id.indexOf("#"));
684
+ var NodeRegistry = class {
685
+ entries = /* @__PURE__ */ new Map();
686
+ /** Ids per part (the text before `#`), so one part can be dropped without scanning the rest. */
687
+ byPart = /* @__PURE__ */ new Map();
688
+ liveIds = /* @__PURE__ */ new WeakMap();
689
+ open = /* @__PURE__ */ new Map();
690
+ ownerOf = /* @__PURE__ */ new WeakMap();
691
+ dirty = /* @__PURE__ */ new Set();
692
+ clear() {
693
+ this.entries.clear();
694
+ this.byPart.clear();
695
+ this.open.clear();
696
+ this.dirty.clear();
697
+ }
698
+ /** Forget every node of one part, live or detached. */
699
+ dropPart(partName) {
700
+ for (const id of this.byPart.get(partName) ?? []) this.entries.delete(id);
701
+ this.byPart.delete(partName);
702
+ for (const [live, record] of this.open) {
703
+ if (partOf(record.id) !== partName) continue;
704
+ this.open.delete(live);
705
+ this.dirty.delete(live);
706
+ }
707
+ }
708
+ index(id) {
709
+ const part = partOf(id);
710
+ let ids = this.byPart.get(part);
711
+ if (!ids) this.byPart.set(part, ids = /* @__PURE__ */ new Set());
712
+ ids.add(id);
713
+ }
714
+ get(id) {
715
+ return this.entries.get(id);
716
+ }
717
+ require(id) {
718
+ let entry = this.entries.get(id) ?? this.resolveInsideParagraph(id);
719
+ if (!entry) throw new RegistryError(`journal names unknown node ${id}`);
720
+ if ("live" in entry && entry.live instanceof Y3__namespace.Map && isRichParagraph(entry.live)) {
721
+ this.openParagraph(entry.live);
722
+ entry = this.entries.get(id);
723
+ }
724
+ if ("plain" in entry) {
725
+ const owner = this.ownerOf.get(entry.plain);
726
+ if (owner) this.dirty.add(owner);
727
+ }
728
+ return entry;
729
+ }
730
+ /** An id inside a paragraph that is not open yet: open the paragraph it belongs to. */
731
+ resolveInsideParagraph(id) {
732
+ const hash = id.indexOf("#");
733
+ if (hash < 0 || id.startsWith("new:", hash + 1)) return void 0;
734
+ const path = id.slice(hash + 1).split(".");
735
+ for (let length = path.length - 1; length >= 1; length--) {
736
+ const ancestor = this.entries.get(id.slice(0, hash + 1) + path.slice(0, length).join("."));
737
+ if (!ancestor) continue;
738
+ if ("live" in ancestor && ancestor.live instanceof Y3__namespace.Map && isRichParagraph(ancestor.live)) {
739
+ this.openParagraph(ancestor.live);
740
+ return this.entries.get(id);
741
+ }
742
+ return void 0;
743
+ }
744
+ return void 0;
745
+ }
746
+ idOf(node) {
747
+ return this.liveIds.get(node);
748
+ }
749
+ isCurrentLive(id, node) {
750
+ const entry = this.entries.get(id);
751
+ return entry !== void 0 && "live" in entry && entry.live === node;
752
+ }
753
+ registerLive(id, node) {
754
+ this.entries.set(id, { live: node });
755
+ this.liveIds.set(node, id);
756
+ this.index(id);
757
+ }
758
+ registerPlain(node, keepNewer = false) {
759
+ const existing = this.entries.get(node.id);
760
+ if (!(keepNewer && existing && "plain" in existing)) {
761
+ this.entries.set(node.id, { plain: node });
762
+ this.index(node.id);
763
+ }
764
+ if (node.type === "element")
765
+ for (const child of node.children) this.registerPlain(child, keepNewer);
766
+ }
767
+ /**
768
+ * Walk a mirror root and register every node under its positional id, `${part}#0` for the root and
769
+ * `${parent}.${index}` below it. This is the id scheme core assigns when it reads a part. A rich
770
+ * paragraph is registered as one node; its inner nodes appear when it is opened.
771
+ */
772
+ registerTree(partName, root) {
773
+ const walk = (node, id) => {
774
+ this.registerLive(id, node);
775
+ if (node instanceof Y3__namespace.Text) return;
776
+ if (isRichParagraph(node)) return;
777
+ childrenOf(node).forEach((child, index) => walk(child, `${id}.${index}`));
778
+ };
779
+ walk(root, `${partName}#0`);
780
+ }
781
+ // ── Rich paragraphs ──────────────────────────────────────────────────────
782
+ own(node, live) {
783
+ if (live) this.ownerOf.set(node, live);
784
+ else this.ownerOf.delete(node);
785
+ if (node.type === "element") for (const child of node.children) this.own(child, live);
786
+ }
787
+ openParagraph(live) {
788
+ const existing = this.open.get(live);
789
+ if (existing) return existing;
790
+ const id = this.liveIds.get(live);
791
+ if (id === void 0) throw new RegistryError("paragraph has no registered id");
792
+ const root = xmlToPlain(liveToXml(live), id);
793
+ const record = { live, root, id };
794
+ this.open.set(live, record);
795
+ this.registerPlain(root, true);
796
+ this.own(root, live);
797
+ return record;
798
+ }
799
+ /** The Yjs paragraph an open plain root stands for. */
800
+ liveOfRoot(node) {
801
+ const live = this.ownerOf.get(node);
802
+ return live && this.open.get(live)?.root === node ? live : void 0;
803
+ }
804
+ /** Parent and index of a node inside an open paragraph. */
805
+ locatePlain(node) {
806
+ const live = this.ownerOf.get(node);
807
+ const record = live && this.open.get(live);
808
+ if (!record) return void 0;
809
+ const search = (parent) => {
810
+ const index = parent.children.indexOf(node);
811
+ if (index >= 0) return { parent, index };
812
+ for (const child of parent.children) {
813
+ if (child.type === "element") {
814
+ const found = search(child);
815
+ if (found) return found;
816
+ }
817
+ }
818
+ return void 0;
819
+ };
820
+ return search(record.root);
821
+ }
822
+ /** Mark the paragraph a plain node belongs to as changed. */
823
+ touch(node) {
824
+ const owner = this.ownerOf.get(node);
825
+ if (owner) this.dirty.add(owner);
826
+ }
827
+ /** Nodes just placed under `parent` belong to the same paragraph as it, if any. */
828
+ adopt(parent, children) {
829
+ const owner = this.ownerOf.get(parent);
830
+ for (const child of children) this.own(child, owner);
831
+ if (owner) this.dirty.add(owner);
832
+ }
833
+ /** Write every changed open paragraph back into the mirror. Call at the end of a journal. */
834
+ flushRich() {
835
+ for (const live of [...this.dirty]) {
836
+ this.dirty.delete(live);
837
+ const record = this.open.get(live);
838
+ if (record) this.writeBack(record);
839
+ }
840
+ }
841
+ writeBack(record) {
842
+ const { live, root } = record;
843
+ const xml = plainToXml(root);
844
+ const encoded = encodeParagraph(xml.children);
845
+ if (!encoded) {
846
+ this.degrade(record);
847
+ return;
848
+ }
849
+ const attrs = attrsOf(live);
850
+ if (JSON.stringify(attrs.toArray()) !== JSON.stringify(xml.attrs)) {
851
+ attrs.delete(0, attrs.length);
852
+ attrs.push(xml.attrs.map((a) => ({ ...a })));
853
+ }
854
+ const bindings = bindingsOf(live);
855
+ if (JSON.stringify(bindings.toArray()) !== JSON.stringify(xml.bindings)) {
856
+ bindings.delete(0, bindings.length);
857
+ bindings.push(xml.bindings.map((b) => ({ ...b })));
858
+ }
859
+ const head = childrenOf(live);
860
+ const stored = head.toArray().map((child) => liveToXml(child));
861
+ if (JSON.stringify(stored) !== JSON.stringify(encoded.head)) {
862
+ head.delete(0, head.length);
863
+ head.push(encoded.head.map((el) => xmlToLive(el)));
864
+ }
865
+ const inline = live.get("inline");
866
+ applyUnits(inline, readUnits(inline), encoded.units);
867
+ }
868
+ /** The paragraph can no longer be held as rich text: store it as plain elements instead. */
869
+ degrade(record) {
870
+ const parent = record.live.parent;
871
+ if (!(parent instanceof Y3__namespace.Array))
872
+ throw new RegistryError("a paragraph outside a parent cannot change form");
873
+ const index = parent.toArray().indexOf(record.live);
874
+ const generic = this.toGenericLive(record.root);
875
+ this.open.delete(record.live);
876
+ parent.delete(index, 1);
877
+ parent.insert(index, [generic]);
878
+ }
879
+ // ── Snapshots and conversion ─────────────────────────────────────────────
880
+ /** Deep copy of a node into plain objects carrying the same ids. */
881
+ snapshot(id) {
882
+ const entry = this.require(id);
883
+ if ("plain" in entry) return entry.plain;
884
+ return this.snapshotLive(entry.live);
885
+ }
886
+ snapshotLive(node) {
887
+ const id = this.liveIds.get(node);
888
+ if (id === void 0) throw new RegistryError("live node has no registered id");
889
+ if (node instanceof Y3__namespace.Text) return { type: "text", id, value: node.toString() };
890
+ if (isRichParagraph(node)) {
891
+ return this.open.get(node)?.root ?? xmlToPlain(liveToXml(node), id);
892
+ }
893
+ const prefix = node.get("prefix");
894
+ const plain = {
895
+ type: "element",
896
+ id,
897
+ ns: node.get("ns"),
898
+ local: node.get("local"),
899
+ attrs: attrsOf(node).toArray().map((a) => ({ ...a })),
900
+ bindings: bindingsOf(node).toArray().map((b) => ({ ...b })),
901
+ children: childrenOf(node).map((child) => this.snapshotLive(child))
902
+ };
903
+ if (prefix !== void 0) plain.prefix = prefix;
904
+ return plain;
905
+ }
906
+ /**
907
+ * Replace the registry entries of a subtree with plain copies, if they still point at these exact
908
+ * live nodes. A node that was already re-registered elsewhere (moved) is left alone.
909
+ */
910
+ detach(node) {
911
+ this.detachInto(node, this.snapshotLive(node));
912
+ }
913
+ detachInto(node, plain) {
914
+ const id = this.liveIds.get(node);
915
+ if (id !== void 0 && this.isCurrentLive(id, node)) {
916
+ this.entries.set(id, { plain });
917
+ }
918
+ if (node instanceof Y3__namespace.Text || plain.type === "text") return;
919
+ if (isRichParagraph(node)) {
920
+ this.open.delete(node);
921
+ this.dirty.delete(node);
922
+ return;
923
+ }
924
+ const liveChildren = childrenOf(node).toArray();
925
+ liveChildren.forEach((child, index) => this.detachInto(child, plain.children[index]));
926
+ }
927
+ /** Build Yjs types for a plain subtree and register every node as live. */
928
+ toLive(plain) {
929
+ if (plain.type === "text") {
930
+ const text = new Y3__namespace.Text();
931
+ text.insert(0, plain.value);
932
+ this.registerLive(plain.id, text);
933
+ return text;
934
+ }
935
+ if (isParagraphEl(plain)) {
936
+ const xml = plainToXml(plain);
937
+ const encoded = encodeParagraph(xml.children);
938
+ if (encoded) {
939
+ const map = createRichParagraph(xml, encoded);
940
+ this.liveIds.set(map, plain.id);
941
+ this.registerPlain(plain);
942
+ this.own(plain, map);
943
+ this.open.set(map, { live: map, root: plain, id: plain.id });
944
+ return map;
945
+ }
946
+ }
947
+ return this.toGenericLive(plain);
948
+ }
949
+ toGenericLive(plain) {
950
+ const map = xmlToGenericShell(plain);
951
+ const children = new Y3__namespace.Array();
952
+ children.push(plain.children.map((child) => this.toLive(child)));
953
+ map.set("children", children);
954
+ this.registerLive(plain.id, map);
955
+ return map;
956
+ }
957
+ /**
958
+ * A live node that can be inserted into a parent. Anything not yet attached is built fresh; an
959
+ * attached node is copied, because a Yjs type cannot have two parents.
960
+ */
961
+ attachable(id) {
962
+ return this.toLive(this.snapshot(id));
963
+ }
964
+ };
965
+ function xmlToGenericShell(plain) {
966
+ const map = new Y3__namespace.Map();
967
+ map.set("ns", plain.ns);
968
+ map.set("local", plain.local);
969
+ if (plain.prefix !== void 0) map.set("prefix", plain.prefix);
970
+ const attrs = new Y3__namespace.Array();
971
+ attrs.push(plain.attrs.map((a) => ({ ...a })));
972
+ map.set("attrs", attrs);
973
+ const bindings = new Y3__namespace.Array();
974
+ bindings.push(plain.bindings.map((b) => ({ ...b })));
975
+ map.set("bindings", bindings);
976
+ return map;
977
+ }
978
+
979
+ // src/journal.ts
980
+ var JournalError = class extends Error {
981
+ constructor(message) {
982
+ super(message);
983
+ this.name = "JournalError";
984
+ }
985
+ };
986
+ function sameAttr(a, qname) {
987
+ return a.ns === qname.namespaceUri && a.local === qname.localName;
988
+ }
989
+ function attrJson(qname, value) {
990
+ const json = { ns: qname.namespaceUri, local: qname.localName, value };
991
+ if (qname.prefix !== void 0) json.prefix = qname.prefix;
992
+ return json;
993
+ }
994
+ function setAttribute(registry, id, qname, value) {
995
+ const entry = registry.require(id);
996
+ if ("plain" in entry) {
997
+ const el = asPlainElement(entry.plain, id);
998
+ const index2 = el.attrs.findIndex((a) => sameAttr(a, qname));
999
+ if (value === null) {
1000
+ if (index2 >= 0) el.attrs.splice(index2, 1);
1001
+ } else if (index2 >= 0) {
1002
+ el.attrs[index2] = attrJson(qname, value);
1003
+ } else {
1004
+ el.attrs.push(attrJson(qname, value));
1005
+ }
1006
+ return;
1007
+ }
1008
+ const attrs = attrsOf(asLiveElement(entry.live, id));
1009
+ const index = attrs.toArray().findIndex((a) => sameAttr(a, qname));
1010
+ if (index >= 0) attrs.delete(index, 1);
1011
+ if (value !== null) {
1012
+ if (index >= 0) attrs.insert(index, [attrJson(qname, value)]);
1013
+ else attrs.push([attrJson(qname, value)]);
1014
+ }
1015
+ }
1016
+ function setNamespaceBinding(registry, id, prefix, uri) {
1017
+ const entry = registry.require(id);
1018
+ const binding = uri === null ? null : { prefix, ns: uri };
1019
+ if ("plain" in entry) {
1020
+ const el = asPlainElement(entry.plain, id);
1021
+ const index2 = el.bindings.findIndex((b) => b.prefix === prefix);
1022
+ if (index2 >= 0) el.bindings.splice(index2, 1);
1023
+ if (binding) {
1024
+ if (index2 >= 0) el.bindings.splice(index2, 0, binding);
1025
+ else el.bindings.push(binding);
1026
+ }
1027
+ return;
1028
+ }
1029
+ const bindings = bindingsOf(asLiveElement(entry.live, id));
1030
+ const index = bindings.toArray().findIndex((b) => b.prefix === prefix);
1031
+ if (index >= 0) bindings.delete(index, 1);
1032
+ if (binding) {
1033
+ if (index >= 0) bindings.insert(index, [binding]);
1034
+ else bindings.push([binding]);
1035
+ }
1036
+ }
1037
+ function asLiveElement(node, id) {
1038
+ if (node instanceof Y3__namespace.Text) throw new JournalError(`${id} is a text node, not an element`);
1039
+ return node;
1040
+ }
1041
+ function asPlainElement(node, id) {
1042
+ if (node.type === "text") throw new JournalError(`${id} is a text node, not an element`);
1043
+ return node;
1044
+ }
1045
+ function spliceText(registry, id, start, deleteCount, insert) {
1046
+ const entry = registry.require(id);
1047
+ if ("plain" in entry) {
1048
+ const text2 = entry.plain;
1049
+ if (text2.type !== "text") throw new JournalError(`${id} is not a text node`);
1050
+ text2.value = text2.value.slice(0, start) + insert + text2.value.slice(start + deleteCount);
1051
+ return;
1052
+ }
1053
+ const text = entry.live;
1054
+ if (!(text instanceof Y3__namespace.Text)) throw new JournalError(`${id} is not a text node`);
1055
+ if (start < 0 || start + deleteCount > text.length) {
1056
+ throw new JournalError(`text splice out of range on ${id}`);
1057
+ }
1058
+ if (deleteCount > 0) text.delete(start, deleteCount);
1059
+ if (insert.length > 0) text.insert(start, insert);
1060
+ }
1061
+ function detachRemoved(registry, parent, start, count) {
1062
+ for (let i = start; i < start + count; i++) registry.detach(parent.get(i));
1063
+ }
1064
+ function spliceChildren(registry, parentId, start, deleteCount, childIds) {
1065
+ const parentEntry = registry.require(parentId);
1066
+ if ("plain" in parentEntry) {
1067
+ const parent = asPlainElement(parentEntry.plain, parentId);
1068
+ if (start < 0 || start + deleteCount > parent.children.length) {
1069
+ throw new JournalError(`child splice out of range on ${parentId}`);
1070
+ }
1071
+ const inserted2 = childIds.map((id) => registry.snapshot(id));
1072
+ parent.children.splice(start, deleteCount, ...inserted2);
1073
+ registry.adopt(parent, inserted2);
1074
+ return;
1075
+ }
1076
+ const children = childrenOf(asLiveElement(parentEntry.live, parentId));
1077
+ if (start < 0 || start + deleteCount > children.length) {
1078
+ throw new JournalError(`child splice out of range on ${parentId}`);
1079
+ }
1080
+ detachRemoved(registry, children, start, deleteCount);
1081
+ const inserted = childIds.map((id) => registry.attachable(id));
1082
+ if (deleteCount > 0) children.delete(start, deleteCount);
1083
+ if (inserted.length > 0) children.insert(start, inserted);
1084
+ }
1085
+ function locate(registry, id, node) {
1086
+ const children = node.parent;
1087
+ if (!(children instanceof Y3__namespace.Array)) throw new JournalError(`${id} is not attached`);
1088
+ const index = children.toArray().indexOf(node);
1089
+ if (index < 0) throw new JournalError(`${id} is not in its parent`);
1090
+ return { children, index };
1091
+ }
1092
+ function moveNode(registry, id, destinationId, destinationIndex) {
1093
+ const entry = registry.require(id);
1094
+ const live = "live" in entry ? entry.live : registry.liveOfRoot(entry.plain);
1095
+ const copy = registry.snapshot(id);
1096
+ if (live) {
1097
+ const { children: source, index } = locate(registry, id, live);
1098
+ registry.detach(live);
1099
+ source.delete(index, 1);
1100
+ } else {
1101
+ const plain = "plain" in entry ? entry.plain : void 0;
1102
+ const where = plain && registry.locatePlain(plain);
1103
+ if (!where) throw new JournalError(`cannot move detached node ${id}`);
1104
+ where.parent.children.splice(where.index, 1);
1105
+ registry.touch(where.parent);
1106
+ }
1107
+ const destinationEntry = registry.require(destinationId);
1108
+ if ("plain" in destinationEntry) {
1109
+ const destination2 = asPlainElement(destinationEntry.plain, destinationId);
1110
+ destination2.children.splice(destinationIndex, 0, copy);
1111
+ registry.adopt(destination2, [copy]);
1112
+ return;
1113
+ }
1114
+ const destination = childrenOf(asLiveElement(destinationEntry.live, destinationId));
1115
+ destination.insert(destinationIndex, [registry.toLive(copy)]);
1116
+ }
1117
+ function putNode(registry, effect) {
1118
+ const d = effect.descriptor;
1119
+ if (d.kind === "textValue") {
1120
+ registry.registerPlain({ type: "text", id: d.logicalId, value: "" });
1121
+ return;
1122
+ }
1123
+ const element = {
1124
+ type: "element",
1125
+ id: d.logicalId,
1126
+ ns: d.qname.namespaceUri,
1127
+ local: d.qname.localName,
1128
+ attrs: [],
1129
+ bindings: [],
1130
+ children: []
1131
+ };
1132
+ if (d.qname.prefix !== void 0) element.prefix = d.qname.prefix;
1133
+ registry.registerPlain(element);
1134
+ }
1135
+ function rootFor(registry, id) {
1136
+ const entry = registry.require(id);
1137
+ const live = "live" in entry && entry.live.parent === null ? entry.live : registry.attachable(id);
1138
+ return asLiveElement(live, id);
1139
+ }
1140
+ function applyEffect(doc, registry, effect, context) {
1141
+ switch (effect.kind) {
1142
+ case "putNode":
1143
+ putNode(registry, effect);
1144
+ return;
1145
+ case "spliceText":
1146
+ spliceText(registry, effect.logicalId, effect.utf16Start, effect.deleteCount, effect.insert);
1147
+ return;
1148
+ case "setAttribute":
1149
+ setAttribute(registry, effect.logicalId, effect.qname, effect.value);
1150
+ return;
1151
+ case "setNamespaceBinding":
1152
+ setNamespaceBinding(registry, effect.logicalId, effect.prefix, effect.uri);
1153
+ return;
1154
+ case "spliceChildren":
1155
+ spliceChildren(
1156
+ registry,
1157
+ effect.parentLogicalId,
1158
+ effect.start,
1159
+ effect.deleteCount,
1160
+ effect.childLogicalIds
1161
+ );
1162
+ return;
1163
+ case "moveNode":
1164
+ moveNode(
1165
+ registry,
1166
+ effect.logicalId,
1167
+ effect.destinationParentLogicalId,
1168
+ effect.destinationIndex
1169
+ );
1170
+ return;
1171
+ case "putXmlPart":
1172
+ doc.getMap("parts").set(effect.name, rootFor(registry, effect.rootLogicalId));
1173
+ return;
1174
+ case "deleteXmlPart":
1175
+ doc.getMap("parts").delete(effect.name);
1176
+ return;
1177
+ case "putRelationship": {
1178
+ const rels = doc.getMap("rels");
1179
+ let byId = rels.get(effect.owner);
1180
+ if (!byId) {
1181
+ byId = new Y3__namespace.Map();
1182
+ rels.set(effect.owner, byId);
1183
+ }
1184
+ byId.set(effect.record.id, { ...effect.record });
1185
+ return;
1186
+ }
1187
+ case "deleteRelationship":
1188
+ doc.getMap("rels").get(effect.owner)?.delete(effect.relationshipId);
1189
+ return;
1190
+ case "putContentTypeOverride":
1191
+ doc.getMap("ctOverrides").set(effect.partName.toLowerCase(), effect.mediaType);
1192
+ return;
1193
+ case "deleteContentTypeOverride":
1194
+ doc.getMap("ctOverrides").delete(effect.partName.toLowerCase());
1195
+ return;
1196
+ case "putBinary": {
1197
+ const bytes = context.binary(effect.descriptor.storageKey);
1198
+ if (!bytes) throw new JournalError(`binary ${effect.descriptor.storageKey} is not available`);
1199
+ doc.getMap("binaries").set(effect.descriptor.storageKey, bytes);
1200
+ return;
1201
+ }
1202
+ case "deleteBinary":
1203
+ doc.getMap("binaries").delete(effect.storageKey);
1204
+ return;
1205
+ }
1206
+ }
1207
+ function applyJournal(doc, registry, journal, origin, context) {
1208
+ try {
1209
+ doc.transact(() => {
1210
+ for (const effect of journal.effects) applyEffect(doc, registry, effect, context);
1211
+ registry.flushRich();
1212
+ }, origin);
1213
+ } catch (error) {
1214
+ if (error instanceof JournalError || error instanceof RegistryError) throw error;
1215
+ throw new JournalError(error instanceof Error ? error.message : String(error));
1216
+ }
1217
+ }
1218
+
1219
+ // src/sync.ts
1220
+ var LOCAL_ORIGIN = /* @__PURE__ */ Symbol("arbi-coedit/local");
1221
+ var DEFAULT_REMOTE_DELAY_MS = 40;
1222
+ var MirrorSync = class {
1223
+ doc;
1224
+ registry = new NodeRegistry();
1225
+ cache;
1226
+ port;
1227
+ actorId;
1228
+ onFailure;
1229
+ onLocalJournal;
1230
+ disposers = [];
1231
+ remoteDelayMs;
1232
+ operation = 0;
1233
+ applyingRemote = false;
1234
+ failed = false;
1235
+ remoteDirty = false;
1236
+ remoteOrigin;
1237
+ remoteTimer;
1238
+ /** Parts whose node ids must be re-registered before the next local journal; true means all. */
1239
+ staleParts = null;
1240
+ constructor(options) {
1241
+ this.doc = options.doc;
1242
+ this.port = options.port;
1243
+ this.actorId = options.actorId;
1244
+ this.onFailure = options.onFailure;
1245
+ this.onLocalJournal = options.onLocalJournal;
1246
+ this.remoteDelayMs = options.remoteDelayMs ?? DEFAULT_REMOTE_DELAY_MS;
1247
+ registerMirror(this.doc, this.registry);
1248
+ this.cache = new MaterializeCache(this.doc);
1249
+ this.disposers.push(() => this.cache.destroy());
1250
+ this.disposers.push(
1251
+ this.port.observePrimitiveJournal((journal) => {
1252
+ if (this.failed || this.applyingRemote) return;
1253
+ try {
1254
+ this.refreshRegistry();
1255
+ this.onLocalJournal?.(journal);
1256
+ applyJournal(this.doc, this.registry, journal, LOCAL_ORIGIN, {
1257
+ binary: (key) => this.port.binaryPart(key)
1258
+ });
1259
+ } catch (error) {
1260
+ this.fail("journal", error);
1261
+ }
1262
+ })
1263
+ );
1264
+ const onAfterTransaction = (transaction) => {
1265
+ if (transaction.origin === LOCAL_ORIGIN || this.failed) return;
1266
+ if (transaction.changed.size === 0 && transaction.changedParentTypes.size === 0) return;
1267
+ this.remoteDirty = true;
1268
+ this.remoteOrigin = transaction.origin;
1269
+ if (this.remoteDelayMs <= 0) this.flushRemote();
1270
+ else if (this.remoteTimer === void 0) {
1271
+ this.remoteTimer = setTimeout(() => this.flushRemote(), this.remoteDelayMs);
1272
+ }
1273
+ };
1274
+ this.doc.on("afterTransaction", onAfterTransaction);
1275
+ this.disposers.push(() => this.doc.off("afterTransaction", onAfterTransaction));
1276
+ this.disposers.push(() => clearTimeout(this.remoteTimer));
1277
+ }
1278
+ get hasFailed() {
1279
+ return this.failed;
1280
+ }
1281
+ /** Mirror any journal still queued locally. Call before integrating inbound updates. */
1282
+ beforeRemote() {
1283
+ this.port.flushPendingJournals();
1284
+ }
1285
+ /** Publish any waiting remote change now. Call before a local operation runs. */
1286
+ flushRemote() {
1287
+ clearTimeout(this.remoteTimer);
1288
+ this.remoteTimer = void 0;
1289
+ if (!this.remoteDirty || this.failed) return;
1290
+ this.remoteDirty = false;
1291
+ this.publishRemote(this.remoteOrigin);
1292
+ }
1293
+ /** Make the local tree equal to the room: used when the editor opened an older snapshot of it. */
1294
+ adopt() {
1295
+ this.remoteDirty = false;
1296
+ this.publishRemote(void 0);
1297
+ }
1298
+ /** Re-register node ids for parts a remote publication replaced; deferred until a local edit needs them. */
1299
+ refreshRegistry() {
1300
+ if (this.staleParts === null) return;
1301
+ registerMirror(this.doc, this.registry, this.staleParts === true ? void 0 : this.staleParts);
1302
+ this.staleParts = null;
1303
+ }
1304
+ publishRemote(origin) {
1305
+ this.applyingRemote = true;
1306
+ const changed = this.cache.take();
1307
+ try {
1308
+ const result = this.port.applyRemotePackage(materialize(this.doc, this.cache, changed), {
1309
+ origin: "dev.docx-editor.collaboration.remote",
1310
+ actorId: typeof origin === "string" ? origin : this.actorId,
1311
+ operationId: `${this.actorId}:remote:${this.operation++}`
1312
+ });
1313
+ if (!result.ok) throw new JournalError(result.reason);
1314
+ if (changed === null || this.staleParts === true) this.staleParts = true;
1315
+ else this.staleParts = /* @__PURE__ */ new Set([...this.staleParts ?? [], ...changed]);
1316
+ } catch (error) {
1317
+ this.cache.restore(changed);
1318
+ this.fail(error instanceof MaterializeError ? "materialize" : "apply", error);
1319
+ } finally {
1320
+ this.applyingRemote = false;
1321
+ }
1322
+ }
1323
+ fail(code, error) {
1324
+ if (this.failed) return;
1325
+ this.failed = true;
1326
+ const detail = error instanceof JournalError || error instanceof RegistryError || error instanceof MaterializeError ? error.message : error instanceof Error ? error.message : String(error);
1327
+ this.onFailure?.(code, detail);
1328
+ }
1329
+ destroy() {
1330
+ for (const dispose of this.disposers.splice(0)) dispose();
1331
+ }
1332
+ };
1333
+
1334
+ // src/session.ts
1335
+ var MAX_TEXT = 128;
1336
+ var NEVER = 1e3 * 60 * 60 * 24 * 365;
1337
+ var CollaborationSessionError = class extends Error {
1338
+ code;
1339
+ constructor(code, message) {
1340
+ super(message ?? code);
1341
+ this.name = "CollaborationSessionError";
1342
+ this.code = code;
1343
+ }
1344
+ };
1345
+ function validateIdentity(identity) {
1346
+ const bad = (value) => typeof value !== "string" || value.length === 0 || value.length > MAX_TEXT;
1347
+ if (bad(identity.actorId) || bad(identity.name)) {
1348
+ throw new CollaborationSessionError("invalid-identity");
1349
+ }
1350
+ if (identity.color !== void 0 && collaboration.safeParticipantColor(identity.color) === void 0) {
1351
+ throw new CollaborationSessionError("invalid-identity-color");
1352
+ }
1353
+ return identity;
1354
+ }
1355
+ function readIdentity(value) {
1356
+ if (!value || typeof value !== "object") return void 0;
1357
+ const { actorId, name, color, role } = value;
1358
+ if (typeof actorId !== "string" || actorId.length === 0 || actorId.length > MAX_TEXT)
1359
+ return void 0;
1360
+ if (typeof name !== "string" || name.length === 0 || name.length > MAX_TEXT) return void 0;
1361
+ return {
1362
+ actorId,
1363
+ name,
1364
+ ...typeof color === "string" && collaboration.safeParticipantColor(color) ? { color } : {},
1365
+ ...role === "human" || role === "agent" ? { role } : {}
1366
+ };
1367
+ }
1368
+ var DocxCollaborationSession = class {
1369
+ documentId;
1370
+ sessionId;
1371
+ identity;
1372
+ doc;
1373
+ awareness;
1374
+ offlineEditing;
1375
+ remoteDelayMs;
1376
+ tracker = collaboration.createCollaborationStatusTracker("initializing");
1377
+ statusListeners = /* @__PURE__ */ new Set();
1378
+ participantListeners = /* @__PURE__ */ new Set();
1379
+ selectionListeners = /* @__PURE__ */ new Set();
1380
+ undoManager;
1381
+ sync;
1382
+ port;
1383
+ lastGroup;
1384
+ participantsCache = [];
1385
+ selectionsCache = [];
1386
+ selectionsKey = "[]";
1387
+ baselineVector;
1388
+ disposers = [];
1389
+ constructor(options) {
1390
+ this.documentId = options.documentId;
1391
+ this.identity = validateIdentity(options.identity);
1392
+ this.sessionId = `${options.identity.actorId}:${randomToken()}`;
1393
+ this.doc = options.doc;
1394
+ this.awareness = options.awareness;
1395
+ this.offlineEditing = options.offlineEditing ?? false;
1396
+ this.remoteDelayMs = options.remoteDelayMs;
1397
+ this.undoManager = new Y3__namespace.UndoManager(
1398
+ [
1399
+ this.doc.getMap("parts"),
1400
+ this.doc.getMap("rels"),
1401
+ this.doc.getMap("ctOverrides"),
1402
+ this.doc.getMap("binaries")
1403
+ ],
1404
+ { trackedOrigins: /* @__PURE__ */ new Set([LOCAL_ORIGIN]), captureTimeout: NEVER }
1405
+ );
1406
+ this.awareness.setLocalStateField("user", this.identity);
1407
+ const onAwareness = () => {
1408
+ this.refreshParticipants();
1409
+ this.refreshSelections();
1410
+ };
1411
+ this.awareness.on("change", onAwareness);
1412
+ this.disposers.push(() => this.awareness.off("change", onAwareness));
1413
+ this.refreshParticipants();
1414
+ }
1415
+ // ── Status ───────────────────────────────────────────────────────────────
1416
+ status() {
1417
+ return this.tracker.status();
1418
+ }
1419
+ statusSnapshot() {
1420
+ return this.tracker.snapshot();
1421
+ }
1422
+ subscribeStatus(listener) {
1423
+ this.statusListeners.add(listener);
1424
+ return () => this.statusListeners.delete(listener);
1425
+ }
1426
+ setStatus(status, code, detail) {
1427
+ if (this.tracker.status() === "destroyed") return;
1428
+ if (!this.tracker.set(status, code, detail)) return;
1429
+ for (const listener of [...this.statusListeners]) listener(status, code, detail);
1430
+ }
1431
+ /** The room has synced and holds a document this build understands. */
1432
+ markReady() {
1433
+ if (!isBootstrapped(this.doc)) {
1434
+ this.setStatus("error", "not-initialized");
1435
+ return;
1436
+ }
1437
+ if (readFormat(this.doc) !== COLLAB_FORMAT) {
1438
+ this.setStatus("error", "collaboration-format-mismatch", readFormat(this.doc));
1439
+ return;
1440
+ }
1441
+ this.baselineVector = vectorKey(this.doc);
1442
+ this.setStatus("ready");
1443
+ }
1444
+ markDisconnected() {
1445
+ if (this.status() === "ready") this.setStatus("disconnected", "transport-disconnected");
1446
+ }
1447
+ markReconnected() {
1448
+ if (this.status() === "disconnected") this.setStatus("ready");
1449
+ }
1450
+ markFailed(code, detail) {
1451
+ this.setStatus("error", code, detail);
1452
+ }
1453
+ /** True once an editor has attached its document port. */
1454
+ get attached() {
1455
+ return this.port !== void 0;
1456
+ }
1457
+ gateOperations() {
1458
+ this.sync?.flushRemote();
1459
+ switch (this.status()) {
1460
+ case "ready":
1461
+ return this.port ? null : "collaboration-session-not-attached";
1462
+ case "disconnected":
1463
+ return this.offlineEditing && this.port ? null : "collaboration-session-not-ready";
1464
+ case "destroyed":
1465
+ return "collaboration-session-destroyed";
1466
+ default:
1467
+ return "collaboration-session-not-ready";
1468
+ }
1469
+ }
1470
+ // ── Attachment ───────────────────────────────────────────────────────────
1471
+ attach(port) {
1472
+ if (this.port) {
1473
+ this.markFailed("port-already-attached");
1474
+ return () => {
1475
+ };
1476
+ }
1477
+ if (port.documentId !== this.documentId) {
1478
+ this.markFailed("document-id-mismatch");
1479
+ return () => {
1480
+ };
1481
+ }
1482
+ this.port = port;
1483
+ const sync = new MirrorSync({
1484
+ doc: this.doc,
1485
+ port,
1486
+ actorId: this.identity.actorId,
1487
+ remoteDelayMs: this.remoteDelayMs,
1488
+ onFailure: (code, detail) => {
1489
+ this.markFailed(code === "journal" ? "local-mirror-failed" : "remote-apply-failed", detail);
1490
+ },
1491
+ onLocalJournal: (journal) => {
1492
+ const group = replication.historyGroupOfJournal(journal);
1493
+ if (group === void 0 || group !== this.lastGroup) this.undoManager.stopCapturing();
1494
+ this.lastGroup = group;
1495
+ }
1496
+ });
1497
+ this.sync = sync;
1498
+ if (this.baselineVector !== void 0 && this.baselineVector !== vectorKey(this.doc)) {
1499
+ sync.adopt();
1500
+ }
1501
+ const onPortChange = port.subscribe(() => this.refreshSelections());
1502
+ return () => {
1503
+ onPortChange();
1504
+ sync.destroy();
1505
+ if (this.sync === sync) this.sync = void 0;
1506
+ if (this.port === port) this.port = void 0;
1507
+ };
1508
+ }
1509
+ /** Transport hook: call before integrating inbound Yjs data so queued local edits are mirrored first. */
1510
+ beforeRemote() {
1511
+ this.sync?.beforeRemote();
1512
+ }
1513
+ // ── Undo ─────────────────────────────────────────────────────────────────
1514
+ canUndo() {
1515
+ return this.undoManager.undoStack.length > 0;
1516
+ }
1517
+ canRedo() {
1518
+ return this.undoManager.redoStack.length > 0;
1519
+ }
1520
+ undo() {
1521
+ this.flushPendingJournals();
1522
+ if (this.gateOperations() !== null || !this.canUndo()) return false;
1523
+ this.undoManager.stopCapturing();
1524
+ this.undoManager.undo();
1525
+ this.sync?.flushRemote();
1526
+ return true;
1527
+ }
1528
+ redo() {
1529
+ this.flushPendingJournals();
1530
+ if (this.gateOperations() !== null || !this.canRedo()) return false;
1531
+ this.undoManager.stopCapturing();
1532
+ this.undoManager.redo();
1533
+ this.sync?.flushRemote();
1534
+ return true;
1535
+ }
1536
+ // ── Presence ─────────────────────────────────────────────────────────────
1537
+ setLocalSelection(selection) {
1538
+ this.awareness.setLocalStateField("selection", selection);
1539
+ }
1540
+ participants() {
1541
+ return this.participantsCache;
1542
+ }
1543
+ subscribeParticipants(listener) {
1544
+ this.participantListeners.add(listener);
1545
+ return () => this.participantListeners.delete(listener);
1546
+ }
1547
+ remoteSelections() {
1548
+ return this.selectionsCache;
1549
+ }
1550
+ subscribeRemoteSelections(listener) {
1551
+ this.selectionListeners.add(listener);
1552
+ return () => this.selectionListeners.delete(listener);
1553
+ }
1554
+ refreshParticipants() {
1555
+ const byActor = /* @__PURE__ */ new Map();
1556
+ for (const [clientId, state] of this.awareness.getStates()) {
1557
+ const identity = readIdentity(state.user);
1558
+ if (!identity) continue;
1559
+ const isLocal = clientId === this.awareness.clientID;
1560
+ const existing = byActor.get(identity.actorId);
1561
+ byActor.set(identity.actorId, { ...identity, isLocal: isLocal || existing?.isLocal === true });
1562
+ }
1563
+ const next = [...byActor.values()].sort((a, b) => Number(b.isLocal) - Number(a.isLocal));
1564
+ const same2 = next.length === this.participantsCache.length && next.every((p, i) => {
1565
+ const previous = this.participantsCache[i];
1566
+ return p.actorId === previous.actorId && p.name === previous.name && p.color === previous.color && p.isLocal === previous.isLocal;
1567
+ });
1568
+ if (same2) return;
1569
+ this.participantsCache = next;
1570
+ for (const listener of [...this.participantListeners]) listener(next);
1571
+ }
1572
+ refreshSelections() {
1573
+ const port = this.port;
1574
+ const next = [];
1575
+ if (port) {
1576
+ for (const [clientId, state] of this.awareness.getStates()) {
1577
+ if (clientId === this.awareness.clientID) continue;
1578
+ const record = state;
1579
+ const identity = readIdentity(record.user);
1580
+ const selection = record.selection;
1581
+ if (!identity || !selection) continue;
1582
+ const anchor = resolveAddress(port, selection.anchor);
1583
+ const head = resolveAddress(port, selection.head);
1584
+ if (!anchor || !head) continue;
1585
+ next.push({
1586
+ actorId: identity.actorId,
1587
+ name: identity.name,
1588
+ ...identity.color ? { color: identity.color } : {},
1589
+ anchor,
1590
+ head,
1591
+ ...selection.kind ? { kind: selection.kind } : {}
1592
+ });
1593
+ }
1594
+ }
1595
+ const key = JSON.stringify(next);
1596
+ if (key === this.selectionsKey) return;
1597
+ this.selectionsKey = key;
1598
+ this.selectionsCache = next;
1599
+ for (const listener of [...this.selectionListeners]) listener(next);
1600
+ }
1601
+ // ── Lifecycle ────────────────────────────────────────────────────────────
1602
+ flushPendingJournals() {
1603
+ this.port?.flushPendingJournals();
1604
+ }
1605
+ destroy() {
1606
+ if (this.status() === "destroyed") return;
1607
+ this.flushPendingJournals();
1608
+ this.sync?.destroy();
1609
+ this.sync = void 0;
1610
+ this.port = void 0;
1611
+ this.undoManager.destroy();
1612
+ this.awareness.setLocalState(null);
1613
+ for (const dispose of this.disposers.splice(0)) dispose();
1614
+ this.setStatus("destroyed");
1615
+ this.statusListeners.clear();
1616
+ this.participantListeners.clear();
1617
+ this.selectionListeners.clear();
1618
+ }
1619
+ };
1620
+ function resolveAddress(port, address) {
1621
+ if (!address || typeof address.paragraphId !== "string" || !Number.isInteger(address.offset)) {
1622
+ return void 0;
1623
+ }
1624
+ const paragraph = port.paragraphByStableId(address.paragraphId);
1625
+ if (!paragraph) return void 0;
1626
+ return {
1627
+ paragraphId: paragraph.paragraphId,
1628
+ nodeId: paragraph.nodeId,
1629
+ offset: Math.max(0, Math.min(address.offset, paragraph.text.length))
1630
+ };
1631
+ }
1632
+ function vectorKey(doc) {
1633
+ return Array.from(Y3__namespace.encodeStateVector(doc)).join(",");
1634
+ }
1635
+ function randomToken() {
1636
+ if (typeof crypto.randomUUID === "function") return crypto.randomUUID();
1637
+ const bytes = crypto.getRandomValues(new Uint8Array(16));
1638
+ return Array.from(bytes, (byte) => byte.toString(16).padStart(2, "0")).join("");
1639
+ }
1640
+
1641
+ // src/hocuspocus.ts
1642
+ var DEFAULT_SYNC_TIMEOUT_MS = 2e4;
1643
+ var CollaborationJoinError = class extends Error {
1644
+ code;
1645
+ constructor(code, message) {
1646
+ super(message);
1647
+ this.name = "CollaborationJoinError";
1648
+ this.code = code;
1649
+ }
1650
+ };
1651
+ async function createHocuspocusCollaboration(options) {
1652
+ const ydoc = new Y3__namespace.Doc();
1653
+ const awareness$1 = new awareness.Awareness(ydoc);
1654
+ const session = new DocxCollaborationSession({
1655
+ doc: ydoc,
1656
+ awareness: awareness$1,
1657
+ documentId: options.roomId,
1658
+ identity: options.identity,
1659
+ offlineEditing: options.offlineEditing
1660
+ });
1661
+ let firstSync;
1662
+ const joined = new Promise((resolve, reject) => {
1663
+ firstSync = { resolve, reject };
1664
+ });
1665
+ let hasSynced = false;
1666
+ const provider$1 = new provider.HocuspocusProvider({
1667
+ url: options.url,
1668
+ name: options.roomId,
1669
+ document: ydoc,
1670
+ awareness: awareness$1,
1671
+ token: options.token,
1672
+ // Runs before inbound data is applied, so queued local edits reach the room first.
1673
+ onMessage: () => session.beforeRemote(),
1674
+ onSynced: ({ state }) => {
1675
+ if (!state) return;
1676
+ if (!hasSynced) {
1677
+ hasSynced = true;
1678
+ firstSync?.resolve();
1679
+ } else {
1680
+ session.markReconnected();
1681
+ }
1682
+ },
1683
+ onDisconnect: () => session.markDisconnected(),
1684
+ onClose: () => session.markDisconnected(),
1685
+ onAuthenticationFailed: ({ reason }) => {
1686
+ session.markFailed("authentication-failed", reason);
1687
+ firstSync?.reject(new CollaborationJoinError("authentication-failed", reason));
1688
+ }
1689
+ });
1690
+ const timeout = setTimeout(() => {
1691
+ firstSync?.reject(
1692
+ new CollaborationJoinError("initialization-timeout", "Could not join the document in time.")
1693
+ );
1694
+ }, options.syncTimeoutMs ?? DEFAULT_SYNC_TIMEOUT_MS);
1695
+ const destroy = () => {
1696
+ clearTimeout(timeout);
1697
+ session.destroy();
1698
+ provider$1.destroy();
1699
+ ydoc.destroy();
1700
+ };
1701
+ try {
1702
+ await joined;
1703
+ session.markReady();
1704
+ if (session.status() === "error") {
1705
+ throw new CollaborationJoinError(
1706
+ "initialization-aborted",
1707
+ session.statusSnapshot().reason?.code ?? "The document could not be opened."
1708
+ );
1709
+ }
1710
+ const document = materializeBytes(ydoc);
1711
+ return { ydoc, provider: provider$1, session, document, destroy };
1712
+ } catch (error) {
1713
+ destroy();
1714
+ throw error;
1715
+ } finally {
1716
+ clearTimeout(timeout);
1717
+ }
1718
+ }
1719
+
1720
+ exports.CollaborationJoinError = CollaborationJoinError;
1721
+ exports.createHocuspocusCollaboration = createHocuspocusCollaboration;
1722
+ //# sourceMappingURL=hocuspocus.cjs.map
1723
+ //# sourceMappingURL=hocuspocus.cjs.map