yrby-client 0.4.3 → 0.6.0

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.
Files changed (44) hide show
  1. package/README.md +250 -36
  2. package/dist/actioncable_provider.d.ts +26 -1
  3. package/dist/actioncable_provider.d.ts.map +1 -1
  4. package/dist/actioncable_provider.js +295 -150
  5. package/dist/actioncable_provider.js.map +1 -1
  6. package/dist/cjs/actioncable_provider.d.ts +26 -1
  7. package/dist/cjs/actioncable_provider.js +296 -151
  8. package/dist/cjs/document_element.d.ts +24 -0
  9. package/dist/cjs/document_element.js +260 -0
  10. package/dist/cjs/document_session.d.ts +62 -0
  11. package/dist/cjs/document_session.js +335 -0
  12. package/dist/cjs/index.d.ts +2 -0
  13. package/dist/cjs/index.js +4 -1
  14. package/dist/cjs/reliable_sync.d.ts +18 -31
  15. package/dist/cjs/reliable_sync.js +128 -98
  16. package/dist/cjs/turbo_adapter.d.ts +9 -0
  17. package/dist/cjs/turbo_adapter.js +81 -0
  18. package/dist/cjs/y_protocol_session.d.ts +7 -14
  19. package/dist/cjs/y_protocol_session.js +97 -92
  20. package/dist/document_element.d.ts +25 -0
  21. package/dist/document_element.d.ts.map +1 -0
  22. package/dist/document_element.js +225 -0
  23. package/dist/document_element.js.map +1 -0
  24. package/dist/document_session.d.ts +63 -0
  25. package/dist/document_session.d.ts.map +1 -0
  26. package/dist/document_session.js +296 -0
  27. package/dist/document_session.js.map +1 -0
  28. package/dist/index.d.ts +2 -0
  29. package/dist/index.d.ts.map +1 -1
  30. package/dist/index.js +2 -0
  31. package/dist/index.js.map +1 -1
  32. package/dist/reliable_sync.d.ts +18 -31
  33. package/dist/reliable_sync.d.ts.map +1 -1
  34. package/dist/reliable_sync.js +128 -98
  35. package/dist/reliable_sync.js.map +1 -1
  36. package/dist/turbo_adapter.d.ts +10 -0
  37. package/dist/turbo_adapter.d.ts.map +1 -0
  38. package/dist/turbo_adapter.js +78 -0
  39. package/dist/turbo_adapter.js.map +1 -0
  40. package/dist/y_protocol_session.d.ts +7 -14
  41. package/dist/y_protocol_session.d.ts.map +1 -1
  42. package/dist/y_protocol_session.js +97 -92
  43. package/dist/y_protocol_session.js.map +1 -1
  44. package/package.json +29 -6
@@ -12,10 +12,11 @@ exports.ActionCableProvider = void 0;
12
12
  // the server's persistence/ack path.
13
13
  //
14
14
  // The constructor does not auto-connect: wire up your editor binding first, then
15
- // call `connect()`. Watch the connection with `onStatusChange(({ status }) => ...)`
16
- // or the `status` getter. On `disconnect()`/`destroy()`, and on browser
17
- // `pagehide`, the provider broadcasts a presence removal so peers drop our cursor
18
- // right away instead of waiting for the awareness timeout.
15
+ // call `connect()`. Watch the connection with `onStatusChange(({ status, pending }) => ...)`
16
+ // or the `status` and `hasPending` getters. Editors that must not bind before the first sync
17
+ // can `await provider.whenSynced` after connect(). On `disconnect()`/`destroy()`,
18
+ // and on browser `pagehide`, the provider broadcasts a presence removal so peers
19
+ // drop our cursor right away instead of waiting for the awareness timeout.
19
20
  const y_protocol_session_js_1 = require("./y_protocol_session.js");
20
21
  const base64_js_1 = require("./base64.js");
21
22
  const awareness_1 = require("y-protocols/awareness");
@@ -26,27 +27,53 @@ class ActionCableProvider {
26
27
  channelParams;
27
28
  awareness;
28
29
  session;
29
- #subscription = null;
30
+ #state = { phase: "disconnected" };
30
31
  #onError;
31
- #connected = false;
32
- #status = "disconnected";
32
+ // The last event sent to listeners. A refresh notifies them only when a field differs.
33
+ #last = { status: "disconnected", pending: false };
33
34
  #statusListeners = new Set();
34
- #onUnload = null;
35
- #onRestore = null;
36
- #stashedPresence = null;
35
+ #resolveSynced;
36
+ #onDocUpdate = () => this.#refreshStatus(); // a local edit may have made the queue non-empty
37
+ #page = null;
38
+ /**
39
+ * Resolves once the document has first caught up with the server. Most
40
+ * editor bindings seed an empty document when they mount, so binding
41
+ * before the server's state arrives makes each client insert its own
42
+ * top-level node. Create the editor after this resolves:
43
+ *
44
+ * provider.connect();
45
+ * await provider.whenSynced;
46
+ * // now hand the doc to the editor binding
47
+ *
48
+ * It resolves on the first catch-up and remains resolved across later
49
+ * reconnects, even while `synced` is false during a re-handshake. Use
50
+ * `onStatusChange` to track the live connection. If the provider is
51
+ * destroyed before the first sync, it never resolves.
52
+ */
53
+ whenSynced = new Promise((resolve) => { this.#resolveSynced = resolve; });
37
54
  constructor(doc, consumer, channelName, channelParams = {}, opts = {}) {
38
55
  this.doc = doc;
39
56
  this.consumer = consumer;
40
57
  this.channelName = channelName;
41
58
  this.channelParams = channelParams;
42
- this.awareness = new awareness_1.Awareness(doc);
43
- this.#onError = opts.onError ?? ((error, context) => console.warn(`[yrby] ${context}:`, error));
59
+ const onError = opts.onError ?? ((error, context) => console.warn(`[yrby] ${context}:`, error));
60
+ this.#onError = (error, context) => {
61
+ try {
62
+ onError(error, context);
63
+ }
64
+ catch (callbackError) {
65
+ console.warn("[yrby] onError callback failed:", callbackError, "while reporting:", error);
66
+ }
67
+ };
68
+ this.awareness = new ProviderAwareness(doc, this.#onError);
44
69
  this.session = new y_protocol_session_js_1.YProtocolSession(doc, {
45
70
  awareness: this.awareness,
46
71
  resendInterval: opts.resendInterval,
47
72
  onError: this.#onError,
48
73
  send: (frame, id) => this.#send(frame, id),
49
74
  });
75
+ // Registered after the session's listener, so the queue already has the edit.
76
+ this.doc.on("update", this.#onDocUpdate);
50
77
  }
51
78
  /** True once the document has caught up with the server (received a SyncStep2). */
52
79
  get synced() {
@@ -73,7 +100,7 @@ class ActionCableProvider {
73
100
  }
74
101
  /** Current connection status. See {@link ProviderStatus}. */
75
102
  get status() {
76
- return this.#status;
103
+ return this.#computeStatus();
77
104
  }
78
105
  /** Subscribe to status changes. Returns an unsubscribe function. */
79
106
  onStatusChange(listener) {
@@ -81,171 +108,289 @@ class ActionCableProvider {
81
108
  return () => this.#statusListeners.delete(listener);
82
109
  }
83
110
  connect() {
84
- if (this.#subscription)
85
- return;
86
- const provider = this;
87
- this.#subscription = this.consumer.subscriptions.create({ channel: this.channelName, ...this.channelParams }, {
88
- received(message) {
89
- // Reliable-delivery ack: confirm + prune the local queue. `dropped`
90
- // marks a settle-without-record (unhealable gap) — the session prunes
91
- // either way but surfaces the drop via onError("ack-dropped").
92
- if (message && message.ack !== undefined) {
93
- provider.session.ack(message.ack, message.dropped === true);
94
- return;
95
- }
96
- const awarenessPayload = message && message.awareness;
97
- const payload = message && (awarenessPayload ?? message.update);
98
- if (typeof payload !== "string")
99
- return;
100
- // Guard base64 decode too: a malformed envelope must not throw into
101
- // the cable callback (session.receive is itself defensive).
102
- let frame;
103
- try {
104
- frame = (0, base64_js_1.fromBase64)(payload);
105
- }
106
- catch (error) {
107
- provider.#onError(error, "received");
108
- return;
109
- }
110
- if (awarenessPayload !== undefined && frame[0] !== y_protocol_session_js_1.MessageType.Awareness) {
111
- provider.#onError(new Error("awareness envelope carried a non-awareness frame"), "received");
112
- return;
113
- }
114
- const reply = provider.session.receive(frame);
115
- if (reply)
116
- provider.#send(reply, undefined); // e.g. SyncStep2 answering a SyncStep1
117
- provider.#refreshStatus(); // a SyncStep2 may have just flipped us to "synced"
118
- },
119
- connected() {
120
- provider.#connected = true;
121
- provider.session.onConnect(); // handshake + replay the unacked tail
122
- provider.#refreshStatus();
123
- },
124
- disconnected() {
125
- provider.#connected = false;
126
- provider.session.onDisconnect(); // pause retransmits, clear remote presence
127
- provider.#refreshStatus(); // subscription still set -> "connecting" (retrying)
128
- },
129
- rejected() {
130
- // The channel refused the subscription (auth, missing doc). Surface
131
- // it and tear down — otherwise the provider sits at "connecting"
132
- // forever, silently queueing edits. The app decides what's next.
133
- provider.#onError(new Error("subscription rejected by the server"), "rejected");
134
- provider.disconnect();
135
- },
136
- });
137
- this.#installUnloadHandler();
138
- this.#refreshStatus(); // -> "connecting"
139
- }
140
- disconnect() {
141
- if (!this.#subscription)
142
- return;
143
- const sub = this.#subscription;
144
- // Tell peers we're gone while the transport is still live, then pause and
145
- // detach. Defer the unsubscribe one microtask so the removal frame flushes
146
- // before the channel tears down.
147
- this.session.removeLocalAwareness();
148
- this.session.onDisconnect();
149
- this.#connected = false;
150
- this.#subscription = null;
151
- this.#removeUnloadHandler();
152
- // Universal teardown: both @rails/actioncable and @anycable/web subscriptions
153
- // expose unsubscribe() (Rails' just calls consumer.subscriptions.remove(this)
154
- // internally). @anycable has NO consumer.subscriptions.remove, so calling that
155
- // would throw there.
156
- queueMicrotask(() => sub.unsubscribe?.());
157
- this.#refreshStatus(); // -> "disconnected"
158
- }
159
- destroy() {
111
+ if (this.#destroying())
112
+ throw new Error("provider is destroyed");
113
+ if (this.#state.phase !== "disconnected")
114
+ return;
115
+ const attempt = {};
116
+ this.#state = { phase: "subscribing", attempt };
117
+ // A consumer may invoke callbacks inside create(), before the subscription
118
+ // is installed, so on() defers those by one microtask.
119
+ const on = (callback) => (...args) => {
120
+ const run = () => { if (this.#active(attempt))
121
+ callback(...args); };
122
+ if (this.#state.phase === "subscribing")
123
+ queueMicrotask(run);
124
+ else
125
+ run();
126
+ };
127
+ let subscription;
128
+ try {
129
+ subscription = this.consumer.subscriptions.create({ channel: this.channelName, ...this.channelParams }, {
130
+ received: on((message) => this.#receive(message, attempt)),
131
+ connected: on(() => this.#connected()),
132
+ disconnected: on(() => this.#lost()),
133
+ rejected: on(() => this.#stop("reject")),
134
+ });
135
+ }
136
+ catch (error) {
137
+ if (!this.#subscribing(attempt))
138
+ return;
139
+ this.#state = { phase: "disconnected" };
140
+ this.#refreshStatus();
141
+ throw error;
142
+ }
143
+ // disconnect() or destroy() ran inside create().
144
+ if (!this.#subscribing(attempt)) {
145
+ this.#unsubscribe(subscription);
146
+ return;
147
+ }
148
+ this.#state = { phase: "connecting", attempt, subscription };
149
+ this.#watchPage();
150
+ this.#refreshStatus();
151
+ }
152
+ disconnect() { this.#stop("disconnect"); }
153
+ /**
154
+ * Resubscribes with updated channel params, such as a renewed grant. This
155
+ * replaces only the cable subscription and keeps the doc, the delivery
156
+ * queue, awareness, and this provider's ack route. Does nothing after
157
+ * destroy().
158
+ */
159
+ renew(params) {
160
+ if (this.#destroying())
161
+ return;
162
+ Object.assign(this.channelParams, params);
160
163
  this.disconnect();
161
- this.session.destroy();
162
- this.awareness.destroy(); // stops its reaper timer
163
- this.#statusListeners.clear();
164
+ this.connect();
164
165
  }
165
- #computeStatus() {
166
- if (!this.#subscription)
167
- return "disconnected";
168
- if (!this.#connected)
169
- return "connecting";
170
- return this.session.synced ? "synced" : "connected";
166
+ destroy() { this.#stop("destroy"); }
167
+ #destroying() {
168
+ const state = this.#state;
169
+ return state.phase === "destroyed" || (state.phase === "stopping" && state.reason === "destroy");
171
170
  }
172
- #refreshStatus() {
173
- const next = this.#computeStatus();
174
- if (next === this.#status)
175
- return;
176
- this.#status = next;
177
- for (const listener of this.#statusListeners)
178
- listener({ status: next });
179
- }
180
- // Presence teardown/restore around page lifecycle:
181
- // - `pagehide`: remove local presence while the socket is still live so peers
182
- // drop our cursor now (bfcache-safe; the awareness timeout is the backstop).
183
- // - `pageshow` with `persisted`: the user came BACK (bfcache restore), so put
184
- // their presence back — editors set awareness once at setup, so without
185
- // this they'd rejoin as a ghost with no cursor.
186
- #installUnloadHandler() {
187
- if (typeof window === "undefined" || this.#onUnload)
188
- return;
189
- this.#onUnload = () => {
190
- this.#stashedPresence = this.awareness.getLocalState();
171
+ #subscribing(attempt) {
172
+ return this.#state.phase === "subscribing" && this.#state.attempt === attempt;
173
+ }
174
+ // Cable callbacks run only for a connecting or connected subscription.
175
+ #active(attempt) {
176
+ const state = this.#state;
177
+ return (state.phase === "connecting" || state.phase === "connected") && state.attempt === attempt;
178
+ }
179
+ // The cable callbacks below run only for the active attempt, which on() in
180
+ // connect() checks.
181
+ #connected() {
182
+ const state = this.#state;
183
+ if (state.phase !== "connecting")
184
+ return;
185
+ this.#state = { ...state, phase: "connected" };
186
+ this.session.resume();
187
+ this.#refreshStatus();
188
+ }
189
+ #lost() {
190
+ const state = this.#state;
191
+ if (state.phase !== "connecting" && state.phase !== "connected")
192
+ return;
193
+ this.#state = { ...state, phase: "connecting" };
194
+ this.session.pause();
195
+ this.#refreshStatus();
196
+ }
197
+ #stop(reason) {
198
+ const state = this.#state;
199
+ if (state.phase === "destroyed")
200
+ return;
201
+ if (state.phase === "stopping") {
202
+ if (reason === "destroy")
203
+ state.reason = reason;
204
+ return;
205
+ }
206
+ if (state.phase === "disconnected" && reason === "disconnect")
207
+ return;
208
+ let finalReason = reason;
209
+ if ("subscription" in state) {
210
+ const stopping = { ...state, phase: "stopping", reason };
211
+ this.#state = stopping;
212
+ this.#unwatchPage();
213
+ // From here the provider ignores the old subscription's callbacks, but the
214
+ // subscription can still send our presence removal before its deferred
215
+ // unsubscribe.
191
216
  this.session.removeLocalAwareness();
192
- };
193
- this.#onRestore = (event) => {
194
- if (!event.persisted || !this.#stashedPresence)
217
+ this.session.pause();
218
+ this.#unsubscribe(state.subscription);
219
+ if (this.#state !== stopping)
195
220
  return;
196
- if (this.awareness.getLocalState() === null) {
197
- this.awareness.setLocalState(this.#stashedPresence);
221
+ finalReason = stopping.reason; // a destroy() during those calls upgrades it
222
+ }
223
+ const destroyed = finalReason === "destroy";
224
+ this.#state = { phase: destroyed ? "destroyed" : "disconnected" };
225
+ if (destroyed)
226
+ this.#destroyOwned();
227
+ else if (finalReason === "reject")
228
+ this.#onError(new Error("subscription rejected by the server"), "rejected");
229
+ this.#refreshStatus();
230
+ if (destroyed)
231
+ this.#statusListeners.clear();
232
+ }
233
+ #destroyOwned() {
234
+ this.session.destroy();
235
+ this.awareness.destroy();
236
+ this.doc.off("update", this.#onDocUpdate);
237
+ }
238
+ #unsubscribe(subscription) {
239
+ queueMicrotask(() => {
240
+ try {
241
+ subscription.unsubscribe?.();
198
242
  }
199
- this.#stashedPresence = null;
200
- };
201
- window.addEventListener("pagehide", this.#onUnload);
202
- window.addEventListener("pageshow", this.#onRestore);
243
+ catch (error) {
244
+ this.#onError(error, "unsubscribe");
245
+ }
246
+ });
203
247
  }
204
- #removeUnloadHandler() {
205
- if (typeof window === "undefined")
248
+ #receive(message, attempt) {
249
+ if (message && message.ack !== undefined) {
250
+ this.session.acknowledge(message.ack);
251
+ this.#refreshStatus();
252
+ return;
253
+ }
254
+ const awarenessPayload = message && message.awareness;
255
+ const payload = message && (awarenessPayload ?? message.update);
256
+ if (typeof payload !== "string")
257
+ return;
258
+ let frame;
259
+ try {
260
+ frame = (0, base64_js_1.fromBase64)(payload);
261
+ }
262
+ catch (error) {
263
+ this.#onError(error, "received");
264
+ return;
265
+ }
266
+ if (awarenessPayload !== undefined && frame[0] !== y_protocol_session_js_1.MessageType.Awareness) {
267
+ this.#onError(new Error("awareness envelope carried a non-awareness frame"), "received");
206
268
  return;
207
- if (this.#onUnload) {
208
- window.removeEventListener("pagehide", this.#onUnload);
209
- this.#onUnload = null;
210
269
  }
211
- if (this.#onRestore) {
212
- window.removeEventListener("pageshow", this.#onRestore);
213
- this.#onRestore = null;
270
+ const reply = this.session.receive(frame);
271
+ // Applying the frame runs doc observers, which may have replaced the connection.
272
+ if (reply && this.#active(attempt))
273
+ this.#send(reply, undefined);
274
+ this.#refreshStatus();
275
+ }
276
+ #computeStatus() {
277
+ switch (this.#state.phase) {
278
+ case "subscribing":
279
+ case "connecting": return "connecting";
280
+ case "connected": return this.session.synced ? "synced" : "connected";
281
+ default: return "disconnected";
282
+ }
283
+ }
284
+ #refreshStatus() {
285
+ const status = this.#computeStatus();
286
+ const pending = this.hasPending;
287
+ if (status === this.#last.status && pending === this.#last.pending)
288
+ return;
289
+ const event = this.#last = { status, pending };
290
+ if (status === "synced")
291
+ this.#resolveSynced();
292
+ // A throwing listener is an application bug. Report it and continue so one
293
+ // bad listener can't stop the others or break the cable callback that
294
+ // triggered this refresh.
295
+ for (const listener of this.#statusListeners) {
296
+ if (this.#last !== event)
297
+ break; // a listener caused a newer transition
298
+ try {
299
+ listener({ status, pending });
300
+ }
301
+ catch (error) {
302
+ this.#onError(error, "listener");
303
+ }
214
304
  }
215
305
  }
306
+ // Handles presence across the page lifecycle. `pagehide` removes our cursor
307
+ // while the socket is still live, so peers drop it without waiting for the
308
+ // awareness timeout. A `pageshow` with `persisted` means the user came back
309
+ // through the bfcache, so we restore the cursor. Editor bindings set
310
+ // awareness only once at setup, so without this the user would return with
311
+ // no cursor.
312
+ #watchPage() {
313
+ if (typeof window === "undefined" || this.#page)
314
+ return;
315
+ let stashed = null;
316
+ const page = this.#page = {
317
+ hide: () => {
318
+ if (this.#page !== page)
319
+ return;
320
+ stashed = this.awareness.getLocalState();
321
+ this.session.removeLocalAwareness();
322
+ },
323
+ show: (event) => {
324
+ if (this.#page !== page || !event.persisted || !stashed)
325
+ return;
326
+ if (this.awareness.getLocalState() === null)
327
+ this.awareness.setLocalState(stashed);
328
+ stashed = null;
329
+ },
330
+ };
331
+ window.addEventListener("pagehide", this.#page.hide);
332
+ window.addEventListener("pageshow", this.#page.show);
333
+ }
334
+ #unwatchPage() {
335
+ if (!this.#page || typeof window === "undefined")
336
+ return;
337
+ const page = this.#page;
338
+ this.#page = null;
339
+ window.removeEventListener("pagehide", page.hide);
340
+ window.removeEventListener("pageshow", page.show);
341
+ }
216
342
  // Send one raw protocol frame over the cable. Awareness frames are whispered
217
343
  // when AnyCable exposes `subscription.whisper`; otherwise they fall back to a
218
344
  // normal send. `id` (reliable doc updates) is tagged onto the envelope so the
219
345
  // server can ack. A no-op while disconnected: reliable frames stay queued in
220
346
  // the session and flush on the next connect().
221
347
  #send(frame, id) {
222
- const sub = this.#subscription;
223
- if (!sub)
348
+ const state = this.#state;
349
+ if (!("subscription" in state))
224
350
  return;
225
- const update = (0, base64_js_1.toBase64)(frame);
226
351
  const isAwareness = frame[0] === y_protocol_session_js_1.MessageType.Awareness;
352
+ if (state.phase === "stopping" && !isAwareness)
353
+ return;
354
+ const { subscription } = state;
355
+ const update = (0, base64_js_1.toBase64)(frame);
227
356
  // Route transport failures (sync throws, or @anycable/web's rejected
228
357
  // promises) to onError instead of letting them escape into update
229
358
  // handlers. A failed send is recoverable: reliable frames stay queued
230
- // until acked, and awareness is best-effort anyway.
359
+ // until acked, and awareness is best-effort anyway. Failures from a
360
+ // subscription that has since been replaced go unreported.
361
+ const report = (error) => {
362
+ const current = this.#state;
363
+ if ("subscription" in current && current.subscription === subscription)
364
+ this.#onError(error, "send");
365
+ };
231
366
  try {
232
- if (isAwareness && typeof sub.whisper === "function") {
233
- this.#observe(sub.whisper({ awareness: update }));
234
- return;
235
- }
236
- const payload = id === undefined ? { update } : { update, id };
237
- this.#observe(sub.send(payload));
367
+ const result = isAwareness && typeof subscription.whisper === "function"
368
+ ? subscription.whisper({ awareness: update })
369
+ : subscription.send(id === undefined ? { update } : { update, id });
370
+ if (result instanceof Promise)
371
+ result.catch(report);
238
372
  }
239
373
  catch (error) {
240
- this.#onError(error, "send");
374
+ report(error);
241
375
  }
242
376
  }
243
- // Attach a rejection handler when a transport returns a promise, so failures
244
- // surface via onError instead of as unhandled rejections.
245
- #observe(result) {
246
- if (result instanceof Promise) {
247
- result.catch((error) => this.#onError(error, "send"));
377
+ }
378
+ exports.ActionCableProvider = ActionCableProvider;
379
+ // Awareness runs application listeners during presence removal and destruction.
380
+ // A failing listener must not interrupt either one or leave the awareness timer
381
+ // running.
382
+ class ProviderAwareness extends awareness_1.Awareness {
383
+ onError;
384
+ constructor(doc, onError) {
385
+ super(doc);
386
+ this.onError = onError;
387
+ }
388
+ emit(...args) {
389
+ try {
390
+ super.emit(...args);
391
+ }
392
+ catch (error) {
393
+ this.onError(error, `awareness:${args[0]}`);
248
394
  }
249
395
  }
250
396
  }
251
- exports.ActionCableProvider = ActionCableProvider;
@@ -0,0 +1,24 @@
1
+ import { type CableConsumer } from "./actioncable_provider.js";
2
+ import { type DocumentSession } from "./document_session.js";
3
+ declare const Base: typeof HTMLElement;
4
+ export declare class YrbyDocumentElement extends Base {
5
+ #private;
6
+ /** Set before adding elements to use another consumer, such as AnyCable's. */
7
+ static consumer: CableConsumer | Promise<CableConsumer> | undefined;
8
+ static observedAttributes: string[];
9
+ get session(): DocumentSession | undefined;
10
+ get doc(): DocumentSession["doc"] | undefined;
11
+ get provider(): DocumentSession["provider"] | undefined;
12
+ /** Resolves after the current attempt's first sync. If the attempt is abandoned, its promise never resolves. */
13
+ get whenSynced(): Promise<void>;
14
+ connectedCallback(): void;
15
+ disconnectedCallback(): void;
16
+ attributeChangedCallback(_name: string, oldValue: string | null, newValue: string | null): void;
17
+ /** @internal Called by the Turbo adapter when the page is live. A new render also retries a stalled document. */
18
+ activate(): void;
19
+ /** @internal Called by the Turbo adapter when the page is cached or previewed. */
20
+ deactivate(): void;
21
+ /** Releases the editor lease. The session keeps any unsaved work. */
22
+ destroy(): void;
23
+ }
24
+ export {};