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
@@ -40,10 +40,10 @@ exports.YProtocolSession = exports.MessageType = void 0;
40
40
  // Awareness); it works in raw Uint8Array frames and leaves the transport to the
41
41
  // caller: base64, the { update, id } / { ack } envelope, and a socket.
42
42
  //
43
- // Call onConnect() when the transport connects, onDisconnect() when it drops,
44
- // ack(id) on an { ack } envelope, and receive(frame) for an inbound frame (it
45
- // returns a reply to send, or null). Local doc and awareness edits send
46
- // themselves via the "update" events.
43
+ // Call resume() when the transport connects, pause() when it drops,
44
+ // acknowledge(id) on an { ack } envelope, and receive(frame) for an inbound
45
+ // frame (it returns a reply to send, or null). The session sends local doc and
46
+ // awareness edits from their "update" events.
47
47
  const yjs_1 = require("yjs");
48
48
  const encoding = __importStar(require("lib0/encoding"));
49
49
  const decoding = __importStar(require("lib0/decoding"));
@@ -58,12 +58,12 @@ class YProtocolSession {
58
58
  awareness;
59
59
  #send;
60
60
  #onError;
61
- #synced = false;
61
+ #state = { phase: "unsynced", cycle: {} };
62
62
  #delivery;
63
63
  #onDocUpdate;
64
64
  #onAwarenessUpdate;
65
65
  constructor(doc, opts) {
66
- const { send, awareness = null, resendInterval, onError, setInterval: setIntervalFn, clearInterval: clearIntervalFn, } = opts ?? {};
66
+ const { send, awareness = null, resendInterval, onError, setInterval: setTimer, clearInterval: clearTimer } = opts ?? {};
67
67
  if (!doc)
68
68
  throw new TypeError("YProtocolSession requires a Y.Doc");
69
69
  if (typeof send !== "function")
@@ -76,8 +76,8 @@ class YProtocolSession {
76
76
  merge: yjs_1.mergeUpdates,
77
77
  send: (update, id) => this.#send(this.#frameUpdate(update), id),
78
78
  resendInterval,
79
- setInterval: setIntervalFn,
80
- clearInterval: clearIntervalFn,
79
+ setInterval: setTimer,
80
+ clearInterval: clearTimer,
81
81
  });
82
82
  this.#onDocUpdate = (update, origin) => {
83
83
  if (origin === this)
@@ -88,10 +88,10 @@ class YProtocolSession {
88
88
  if (this.awareness) {
89
89
  this.#onAwarenessUpdate = ({ added, updated, removed }, origin) => {
90
90
  // Only broadcast our own presence changes. Updates applied from a peer,
91
- // and our own remote-cleanup in onDisconnect, carry origin === this;
91
+ // and our own remote-cleanup in pause(), carry origin === this;
92
92
  // re-sending those would echo presence and broadcast tombstones for
93
93
  // other clients' cursors.
94
- if (origin === this)
94
+ if (origin === this || this.#state.phase === "destroyed")
95
95
  return;
96
96
  const changed = added.concat(updated, removed);
97
97
  this.#send(this.#frameAwareness(changed), undefined); // fire-and-forget
@@ -101,26 +101,35 @@ class YProtocolSession {
101
101
  }
102
102
  /** True once we've received the server's SyncStep2 (the document is caught up). */
103
103
  get synced() {
104
- return this.#synced;
104
+ return this.#state.phase === "synced";
105
105
  }
106
106
  /** True while there are unacknowledged local document updates in flight. */
107
107
  get hasPending() {
108
108
  return this.#delivery.hasPending;
109
109
  }
110
- /** Transport connected: send the opening handshake and replay the unacked tail. */
111
- onConnect() {
110
+ /** Call when the transport is up. Sends the opening handshake, re-announces presence, and replays the unacked tail. */
111
+ resume() {
112
+ if (this.#state.phase === "destroyed")
113
+ return;
114
+ const cycle = {};
115
+ this.#state = { phase: "unsynced", cycle };
112
116
  this.#send(this.#frameSyncStep1(), undefined);
117
+ if (!this.#current(cycle))
118
+ return;
113
119
  if (this.awareness && this.awareness.getLocalState() !== null) {
114
120
  this.#send(this.#frameAwareness([this.doc.clientID]), undefined);
115
121
  }
116
- this.#delivery.onConnect();
122
+ if (this.#current(cycle))
123
+ this.#delivery.resume();
117
124
  }
118
- /** Transport dropped: pause retransmits (queue kept) and clear remote presence. */
119
- onDisconnect() {
120
- this.#synced = false;
121
- this.#delivery.onDisconnect();
125
+ /** Call when the transport is down. Keeps the queue, stops retransmits, and clears peers' presence. */
126
+ pause() {
127
+ if (this.#state.phase === "destroyed")
128
+ return;
129
+ this.#state = { phase: "unsynced", cycle: {} };
130
+ this.#delivery.pause();
122
131
  if (this.awareness) {
123
- const remote = [...this.awareness.getStates().keys()].filter((c) => c !== this.doc.clientID);
132
+ const remote = [...this.awareness.getStates().keys()].filter(c => c !== this.doc.clientID);
124
133
  if (remote.length)
125
134
  (0, awareness_1.removeAwarenessStates)(this.awareness, remote, this);
126
135
  }
@@ -132,23 +141,13 @@ class YProtocolSession {
132
141
  * waiting for the awareness timeout. A no-op when there's no local state.
133
142
  */
134
143
  removeLocalAwareness() {
135
- if (this.awareness && this.awareness.getLocalState() !== null) {
144
+ if (this.#state.phase !== "destroyed" && this.awareness && this.awareness.getLocalState() !== null) {
136
145
  this.awareness.setLocalState(null); // fires "update" -> sends the removal frame
137
146
  }
138
147
  }
139
- /**
140
- * A reliable-delivery `{ ack: id }` envelope arrived. `dropped` is set when
141
- * the server settled the update WITHOUT recording it (rejected as an
142
- * unhealable causal gap after repeated resyncs). The queue is pruned either
143
- * way — retransmitting an unhealable update would loop forever — but a
144
- * dropped settle is surfaced via onError ("ack-dropped") so the app can warn
145
- * or reload instead of silently reporting synced over lost data.
146
- */
147
- ack(id, dropped = false) {
148
- this.#delivery.onAck(id);
149
- if (dropped) {
150
- this.#onError(new Error(`server dropped update ${id} as an unhealable causal gap; it was settled but NOT recorded`), "ack-dropped");
151
- }
148
+ /** A reliable-delivery `{ ack: id }` envelope arrived. */
149
+ acknowledge(id) {
150
+ this.#delivery.acknowledge(id);
152
151
  }
153
152
  /**
154
153
  * Apply an update without treating it as a local edit, so it isn't queued for
@@ -160,11 +159,12 @@ class YProtocolSession {
160
159
  * keystroke becomes an outbound frame), so a bare `Y.applyUpdate(doc, update)`
161
160
  * would look like a local edit and get echoed back on the next connect. Going
162
161
  * through here applies under the session's own origin, which the outbound
163
- * filter skips. Safe to call before `onConnect()`: the state folds into the
162
+ * filter skips. Safe to call before `resume()`: the state folds into the
164
163
  * SyncStep1 handshake instead of being re-sent.
165
164
  */
166
165
  applyRemoteUpdate(update) {
167
- (0, yjs_1.applyUpdate)(this.doc, update, this);
166
+ if (this.#state.phase !== "destroyed")
167
+ (0, yjs_1.applyUpdate)(this.doc, update, this);
168
168
  }
169
169
  /**
170
170
  * Decode and apply one incoming binary protocol frame (document sync or
@@ -172,21 +172,27 @@ class YProtocolSession {
172
172
  * SyncStep1), or null if there's nothing to send.
173
173
  */
174
174
  receive(frame) {
175
+ if (this.#state.phase === "destroyed")
176
+ return null;
177
+ const { cycle } = this.#state;
175
178
  // A malformed/truncated frame must never take down the transport callback:
176
179
  // decode + apply defensively, drop the frame on error, keep the session live.
177
180
  try {
178
- const validatedType = this.#validateFrame(frame);
179
- if (validatedType === null)
180
- return null;
181
+ if (!validateFrame(frame))
182
+ return null; // a y-protocols type yrby doesn't use
181
183
  const decoder = decoding.createDecoder(frame);
182
184
  const encoder = encoding.createEncoder();
183
185
  const type = decoding.readVarUint(decoder);
184
186
  switch (type) {
185
187
  case exports.MessageType.Sync: {
186
188
  encoding.writeVarUint(encoder, exports.MessageType.Sync);
187
- const syncType = (0, sync_1.readSyncMessage)(decoder, encoder, this.doc, this);
188
- if (!this.#synced && syncType === sync_1.messageYjsSyncStep2)
189
- this.#synced = true;
189
+ // y-protocols catches a failing update itself and reports it through this handler.
190
+ const report = (error) => { if (this.#current(cycle))
191
+ this.#onError(error, "receive"); };
192
+ const syncType = (0, sync_1.readSyncMessage)(decoder, encoder, this.doc, this, report);
193
+ if (syncType === sync_1.messageYjsSyncStep2 && this.#current(cycle)) {
194
+ this.#state = { phase: "synced", cycle };
195
+ }
190
196
  break;
191
197
  }
192
198
  case exports.MessageType.Awareness:
@@ -194,22 +200,29 @@ class YProtocolSession {
194
200
  (0, awareness_1.applyAwarenessUpdate)(this.awareness, decoding.readVarUint8Array(decoder), this);
195
201
  break;
196
202
  default:
197
- return null; // a y-protocols type yrby doesn't speak (auth, query-awareness): ignore
203
+ return null; // unreachable, since validateFrame accepts only the two types above
198
204
  }
199
- return encoding.length(encoder) > 1 ? encoding.toUint8Array(encoder) : null;
205
+ return this.#current(cycle) && encoding.length(encoder) > 1 ? encoding.toUint8Array(encoder) : null;
200
206
  }
201
207
  catch (error) {
202
- this.#onError(error, "receive");
208
+ if (this.#current(cycle))
209
+ this.#onError(error, "receive");
203
210
  return null;
204
211
  }
205
212
  }
206
213
  /** Detach doc/awareness listeners and stop retransmits. */
207
214
  destroy() {
215
+ if (this.#state.phase === "destroyed")
216
+ return;
217
+ this.#state = { phase: "destroyed" };
208
218
  this.doc.off("update", this.#onDocUpdate);
209
219
  if (this.awareness && this.#onAwarenessUpdate)
210
220
  this.awareness.off("update", this.#onAwarenessUpdate);
211
221
  this.#delivery.destroy();
212
222
  }
223
+ #current(cycle) {
224
+ return this.#state.phase !== "destroyed" && this.#state.cycle === cycle;
225
+ }
213
226
  #frameSyncStep1() {
214
227
  const e = encoding.createEncoder();
215
228
  encoding.writeVarUint(e, exports.MessageType.Sync);
@@ -228,53 +241,45 @@ class YProtocolSession {
228
241
  encoding.writeVarUint8Array(e, (0, awareness_1.encodeAwarenessUpdate)(this.awareness, clients));
229
242
  return encoding.toUint8Array(e);
230
243
  }
231
- #validateFrame(frame) {
232
- const decoder = decoding.createDecoder(frame);
233
- const type = decoding.readVarUint(decoder);
234
- switch (type) {
235
- case exports.MessageType.Sync: {
236
- const scratchDoc = new yjs_1.Doc();
237
- try {
238
- const scratchEncoder = encoding.createEncoder();
239
- encoding.writeVarUint(scratchEncoder, exports.MessageType.Sync);
240
- (0, sync_1.readSyncMessage)(decoder, scratchEncoder, scratchDoc, this);
241
- }
242
- finally {
243
- scratchDoc.destroy();
244
- }
245
- break;
246
- }
247
- case exports.MessageType.Awareness:
248
- // Validate the payload's CONTENTS, not just the envelope.
249
- // applyAwarenessUpdate mutates state entry by entry and only notifies
250
- // listeners at the end — a bad entry mid-payload would leave earlier
251
- // entries applied with no event fired. Dry-running every entry here
252
- // makes the real apply infallible (and catches trailing garbage
253
- // inside the blob).
254
- {
255
- const payload = decoding.readVarUint8Array(decoder);
256
- const inner = decoding.createDecoder(payload);
257
- const count = decoding.readVarUint(inner);
258
- for (let i = 0; i < count; i++) {
259
- decoding.readVarUint(inner); // clientID
260
- decoding.readVarUint(inner); // clock
261
- JSON.parse(decoding.readVarString(inner)); // state (null on removal)
262
- }
263
- if (decoding.hasContent(inner)) {
264
- throw new Error("awareness payload has trailing bytes");
265
- }
266
- }
267
- break;
268
- default:
269
- return null; // a y-protocols type yrby doesn't speak: ignore
270
- }
271
- // This protocol is one message per frame. Anything left after a complete
272
- // message is malformed (trailing garbage, or low-level packed messages whose
273
- // tail we'd silently drop), so reject it before mutating local state.
274
- if (decoding.hasContent(decoder)) {
275
- throw new Error("frame has trailing bytes after a complete message");
276
- }
277
- return type;
278
- }
279
244
  }
280
245
  exports.YProtocolSession = YProtocolSession;
246
+ // Checks a frame's structure before anything is applied, so a truncated or
247
+ // padded frame changes nothing. Returns false for a frame type yrby ignores
248
+ // (auth, query-awareness) and throws for a malformed one. Yjs checks the
249
+ // contents of an update itself when applying it.
250
+ function validateFrame(frame) {
251
+ const decoder = decoding.createDecoder(frame);
252
+ const type = decoding.readVarUint(decoder);
253
+ if (type === exports.MessageType.Sync) {
254
+ // Every sync message is a subtype followed by one length-prefixed payload.
255
+ decoding.readVarUint(decoder);
256
+ decoding.readVarUint8Array(decoder);
257
+ }
258
+ else if (type === exports.MessageType.Awareness) {
259
+ validateAwareness(decoding.readVarUint8Array(decoder));
260
+ }
261
+ else {
262
+ return false;
263
+ }
264
+ // This protocol is one message per frame. Anything left after a complete
265
+ // message is malformed (trailing garbage, or low-level packed messages whose
266
+ // tail we'd silently drop).
267
+ if (decoding.hasContent(decoder))
268
+ throw new Error("frame has trailing bytes after a complete message");
269
+ return true;
270
+ }
271
+ // applyAwarenessUpdate mutates state entry by entry and notifies listeners
272
+ // only at the end, so a bad entry mid-payload would leave earlier entries
273
+ // applied with no event fired. Reading every entry first makes the real apply
274
+ // infallible and catches trailing bytes inside the payload.
275
+ function validateAwareness(payload) {
276
+ const decoder = decoding.createDecoder(payload);
277
+ const count = decoding.readVarUint(decoder);
278
+ for (let i = 0; i < count; i++) {
279
+ decoding.readVarUint(decoder); // clientID
280
+ decoding.readVarUint(decoder); // clock
281
+ JSON.parse(decoding.readVarString(decoder)); // state (null on removal)
282
+ }
283
+ if (decoding.hasContent(decoder))
284
+ throw new Error("awareness payload has trailing bytes");
285
+ }
@@ -0,0 +1,25 @@
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 {};
25
+ //# sourceMappingURL=document_element.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"document_element.d.ts","sourceRoot":"","sources":["../src/document_element.ts"],"names":[],"mappings":"AAMA,OAAO,EAAE,KAAK,aAAa,EAAE,MAAM,2BAA2B,CAAC;AAC/D,OAAO,EAKL,KAAK,eAAe,EACrB,MAAM,uBAAuB,CAAC;AAI/B,QAAA,MAAM,IAAI,EAAoE,OAAO,WAAW,CAAC;AAkDjG,qBAAa,mBAAoB,SAAQ,IAAI;;IAC3C,8EAA8E;IAC9E,MAAM,CAAC,QAAQ,EAAE,aAAa,GAAG,OAAO,CAAC,aAAa,CAAC,GAAG,SAAS,CAAC;IAGpE,MAAM,CAAC,kBAAkB,WAAgC;IAazD,IAAI,OAAO,IAAI,eAAe,GAAG,SAAS,CAIzC;IACD,IAAI,GAAG,IAAI,eAAe,CAAC,KAAK,CAAC,GAAG,SAAS,CAA8B;IAC3E,IAAI,QAAQ,IAAI,eAAe,CAAC,UAAU,CAAC,GAAG,SAAS,CAAmC;IAC1F,gHAAgH;IAChH,IAAI,UAAU,IAAI,OAAO,CAAC,IAAI,CAAC,CAAoC;IAEnE,iBAAiB,IAAI,IAAI;IAQzB,oBAAoB,IAAI,IAAI;IAC5B,wBAAwB,CAAC,KAAK,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,GAAG,IAAI,EAAE,QAAQ,EAAE,MAAM,GAAG,IAAI,GAAG,IAAI;IAO/F,iHAAiH;IACjH,QAAQ,IAAI,IAAI;IAKhB,kFAAkF;IAClF,UAAU,IAAI,IAAI;IAIlB,qEAAqE;IACrE,OAAO,IAAI,IAAI;CA6HhB"}
@@ -0,0 +1,225 @@
1
+ var _a;
2
+ import { DocumentSessionStore, documentKey, } from "./document_session.js";
3
+ import { registerDocumentMount } from "./turbo_adapter.js";
4
+ // Lets tests and SSR import this module where HTMLElement is undefined.
5
+ const Base = (typeof HTMLElement === "undefined" ? class {
6
+ } : HTMLElement);
7
+ // Holds the application's own inert value while the element forces inert on.
8
+ const INERT_ATTRIBUTE = "data-yrby-inert";
9
+ let sharedConsumer;
10
+ // Loads once per page. A failed import is cleared so a later attempt can retry it.
11
+ function defaultConsumer() {
12
+ sharedConsumer ??= import("@rails/actioncable")
13
+ .then(actioncable => actioncable.createConsumer())
14
+ .catch(error => { sharedConsumer = undefined; throw error; });
15
+ return sharedConsumer;
16
+ }
17
+ function deferred() {
18
+ let resolve;
19
+ const promise = new Promise(r => { resolve = r; });
20
+ return { promise, resolve };
21
+ }
22
+ // Returns the yrby:error detail for a session that ended a lease. A block gets
23
+ // a detail, and a discard gets undefined.
24
+ function blockReport(session) {
25
+ return session.state === "blocked" ? { error: session.error, session } : undefined;
26
+ }
27
+ // The element binds when it is in the page, the Turbo adapter reports the page
28
+ // as live, and it has a descriptor. It abandons an attempt synchronously,
29
+ // because Turbo copies the page as soon as before-cache fires, and after a
30
+ // retarget the editor has to stop writing to the old document before anything
31
+ // else runs. Starting and advancing an attempt and handling every async result
32
+ // all go through #settle, which runs after the current call stack and compares
33
+ // what should be bound with what is.
34
+ export class YrbyDocumentElement extends Base {
35
+ /** Set before adding elements to use another consumer, such as AnyCable's. */
36
+ static consumer;
37
+ // refresh is read when the session is acquired and isn't part of the
38
+ // document's identity, so changing it doesn't rebind the editor.
39
+ static observedAttributes = ["grant", "name", "channel"];
40
+ #live = false; // false while Turbo is showing a cached copy of the page
41
+ #attempt;
42
+ // Key of the document whose session blocked or failed. The element won't retry
43
+ // it until the page renders again, the attributes change, or the element is
44
+ // re-inserted.
45
+ #stalledKey;
46
+ #unregister;
47
+ #settleQueued = false;
48
+ // Resolves when the current attempt first syncs. Abandoning the attempt replaces it.
49
+ #firstSync = deferred();
50
+ get session() {
51
+ // Treat a lease its session aborted as gone, even before settle runs.
52
+ const lease = this.#attempt?.lease;
53
+ return lease && !lease.signal.aborted ? lease.session : undefined;
54
+ }
55
+ get doc() { return this.session?.doc; }
56
+ get provider() { return this.session?.provider; }
57
+ /** Resolves after the current attempt's first sync. If the attempt is abandoned, its promise never resolves. */
58
+ get whenSynced() { return this.#firstSync.promise; }
59
+ connectedCallback() {
60
+ this.#stalledKey = undefined;
61
+ // A same-turn move keeps its attempt, so don't make a live editor inert.
62
+ if (!this.#attempt)
63
+ this.#holdInert();
64
+ this.#unregister ??= registerDocumentMount(this);
65
+ this.#requestSettle();
66
+ }
67
+ // Same-turn moves keep their binding, because settle checks isConnected afterwards.
68
+ disconnectedCallback() { this.#requestSettle(); }
69
+ attributeChangedCallback(_name, oldValue, newValue) {
70
+ if (oldValue === newValue)
71
+ return;
72
+ this.#stalledKey = undefined;
73
+ this.#abandon();
74
+ this.#requestSettle();
75
+ }
76
+ /** @internal Called by the Turbo adapter when the page is live. A new render also retries a stalled document. */
77
+ activate() {
78
+ this.#live = true;
79
+ this.#stalledKey = undefined;
80
+ this.#requestSettle();
81
+ }
82
+ /** @internal Called by the Turbo adapter when the page is cached or previewed. */
83
+ deactivate() {
84
+ this.#live = false;
85
+ this.#abandon();
86
+ }
87
+ /** Releases the editor lease. The session keeps any unsaved work. */
88
+ destroy() {
89
+ // Reset the adapter's last report. The next connection registers again and gets a new one.
90
+ this.#live = false;
91
+ // Clear it before the call, since the call can re-enter through a replacement registration.
92
+ const unregister = this.#unregister;
93
+ this.#unregister = undefined;
94
+ unregister?.();
95
+ this.#abandon();
96
+ }
97
+ #requestSettle() {
98
+ if (this.#settleQueued)
99
+ return;
100
+ this.#settleQueued = true;
101
+ queueMicrotask(() => this.#settle());
102
+ }
103
+ #settle() {
104
+ this.#settleQueued = false;
105
+ if (!this.isConnected) {
106
+ this.destroy();
107
+ return;
108
+ }
109
+ const descriptor = this.#descriptor();
110
+ // key is undefined when nothing should be bound, because the page is cached
111
+ // or the attributes don't name a document yet.
112
+ const key = this.#live && descriptor.grant && descriptor.name ? documentKey(descriptor) : undefined;
113
+ const attempt = this.#attempt;
114
+ if (attempt && attempt.key !== key) {
115
+ // Should not happen, since every change to these facts already abandons the attempt.
116
+ this.#abandon();
117
+ this.#requestSettle();
118
+ return;
119
+ }
120
+ if (key === undefined || key === this.#stalledKey)
121
+ return;
122
+ // Take the next step. An attempt starts, acquires a lease once the consumer
123
+ // loads, and announces once synced. If the attempt has ended, stall.
124
+ if (!attempt)
125
+ this.#start(key, descriptor);
126
+ else if (attempt.ended)
127
+ this.#stall(attempt.ended.detail);
128
+ else if (attempt.consumer && !attempt.lease)
129
+ this.#acquire(attempt, attempt.consumer);
130
+ else if (attempt.synced && !attempt.announced)
131
+ this.#announce(attempt);
132
+ }
133
+ #start(key, descriptor) {
134
+ const attempt = { key, descriptor };
135
+ this.#attempt = attempt;
136
+ Promise.resolve(_a.consumer ?? defaultConsumer()).then(consumer => { attempt.consumer = consumer; }, error => { attempt.ended = { detail: { error } }; }).then(() => this.#requestSettle());
137
+ }
138
+ #acquire(attempt, consumer) {
139
+ let lease;
140
+ try {
141
+ lease = DocumentSessionStore.for(consumer).acquire(attempt.descriptor);
142
+ }
143
+ catch (error) {
144
+ this.#stall({ error });
145
+ return;
146
+ }
147
+ attempt.lease = lease;
148
+ const { session } = lease;
149
+ // A blocked session keeps new leases for retry(), and its first sync may
150
+ // be long past, so an editor must not bind to it.
151
+ const blocked = blockReport(session);
152
+ if (blocked) {
153
+ attempt.ended = { detail: blocked };
154
+ this.#requestSettle();
155
+ return;
156
+ }
157
+ // The lease aborts when its session blocks or is discarded. Read the
158
+ // reason now, before anything retries the session.
159
+ lease.signal.addEventListener("abort", () => {
160
+ attempt.ended ??= { detail: blockReport(session) };
161
+ this.#requestSettle();
162
+ }, { once: true });
163
+ void session.whenSynced.then(() => {
164
+ attempt.synced = true;
165
+ this.#requestSettle();
166
+ });
167
+ }
168
+ #announce(attempt) {
169
+ attempt.announced = true;
170
+ const lease = attempt.lease;
171
+ const { session } = lease;
172
+ this.#restoreInert();
173
+ this.#firstSync.resolve();
174
+ this.dispatchEvent(new CustomEvent("yrby:synced", {
175
+ bubbles: true,
176
+ detail: { session, doc: session.doc, provider: session.provider, lease, signal: lease.signal },
177
+ }));
178
+ }
179
+ #stall(detail) {
180
+ this.#stalledKey = this.#attempt?.key;
181
+ this.#abandon();
182
+ if (detail)
183
+ this.dispatchEvent(new CustomEvent("yrby:error", { bubbles: true, detail }));
184
+ }
185
+ // Ends the current attempt. Releasing the lease runs editor cleanup, which
186
+ // may change attributes or move the element, and those changes only request
187
+ // a settle.
188
+ #abandon() {
189
+ const attempt = this.#attempt;
190
+ if (!attempt)
191
+ return;
192
+ this.#attempt = undefined;
193
+ this.#firstSync = deferred();
194
+ this.#holdInert();
195
+ attempt.lease?.release();
196
+ }
197
+ #descriptor() {
198
+ return {
199
+ channel: this.getAttribute("channel") || undefined,
200
+ grant: this.getAttribute("grant") || "",
201
+ name: this.getAttribute("name") || "",
202
+ refresh: this.getAttribute("refresh") || undefined,
203
+ };
204
+ }
205
+ // Stay inert until synced so nobody types into a document that isn't live yet.
206
+ // The application's own inert value goes in an attribute, which survives a
207
+ // Turbo cache clone, and is restored when the element is ready.
208
+ #holdInert() {
209
+ if (!this.hasAttribute(INERT_ATTRIBUTE))
210
+ this.setAttribute(INERT_ATTRIBUTE, String(this.inert));
211
+ this.inert = true;
212
+ }
213
+ #restoreInert() {
214
+ const saved = this.getAttribute(INERT_ATTRIBUTE);
215
+ if (saved === null)
216
+ return;
217
+ this.inert = saved === "true";
218
+ this.removeAttribute(INERT_ATTRIBUTE);
219
+ }
220
+ }
221
+ _a = YrbyDocumentElement;
222
+ if (typeof customElements !== "undefined" && !customElements.get("yrby-document")) {
223
+ customElements.define("yrby-document", YrbyDocumentElement);
224
+ }
225
+ //# sourceMappingURL=document_element.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"document_element.js","sourceRoot":"","sources":["../src/document_element.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,63 @@
1
+ import * as Y from "yjs";
2
+ import { ActionCableProvider, type CableConsumer } from "./actioncable_provider.js";
3
+ export interface DocumentDescriptor {
4
+ channel?: string;
5
+ grant: string;
6
+ name: string;
7
+ /** A same-origin URL that returns `{ "grant": "..." }` for this document. The session fetches it at most once per rejection. */
8
+ refresh?: string;
9
+ }
10
+ export type ResolvedDescriptor = Readonly<{
11
+ channel: string;
12
+ grant: string;
13
+ name: string;
14
+ refresh?: string;
15
+ }>;
16
+ export type DocumentSessionState = "open" | "blocked" | "closed";
17
+ /** The identity of the document a descriptor names. Matching keys share a session. */
18
+ export declare function documentKey(descriptor: DocumentDescriptor): string;
19
+ declare const attachLease: unique symbol;
20
+ declare const notifyStoreChange: unique symbol;
21
+ /** Holds one consumer's sessions and emits "change" with the session in `detail`. */
22
+ export declare class DocumentSessionStore extends EventTarget {
23
+ #private;
24
+ readonly consumer: CableConsumer;
25
+ static for(consumer: CableConsumer): DocumentSessionStore;
26
+ private constructor();
27
+ get sessions(): readonly DocumentSession[];
28
+ /** Acquire a lease on this document session, creating it on first use. */
29
+ acquire(input: DocumentDescriptor): DocumentLease;
30
+ [notifyStoreChange](session: DocumentSession): void;
31
+ }
32
+ /** A caller's hold on a session. Release it when you're done with the session. */
33
+ export declare class DocumentLease {
34
+ #private;
35
+ readonly session: DocumentSession;
36
+ constructor(session: DocumentSession, onRelease: () => void);
37
+ /** Aborts when the lease ends, including when the session blocks or is discarded. */
38
+ get signal(): AbortSignal;
39
+ setPresence(state: Record<string, unknown> | null): void;
40
+ /** Runs editor cleanup synchronously, before the final check for pending work. */
41
+ release(): void;
42
+ }
43
+ export declare class DocumentSession {
44
+ #private;
45
+ readonly store: DocumentSessionStore;
46
+ readonly descriptor: ResolvedDescriptor;
47
+ private readonly remove;
48
+ readonly doc: Y.Doc;
49
+ readonly provider: ActionCableProvider;
50
+ /** Create and hold sessions through DocumentSessionStore.acquire. */
51
+ constructor(store: DocumentSessionStore, descriptor: ResolvedDescriptor, remove: () => void);
52
+ get error(): unknown;
53
+ get hasPending(): boolean;
54
+ get whenSynced(): Promise<void>;
55
+ get state(): DocumentSessionState;
56
+ [attachLease](): DocumentLease;
57
+ /** Reconnects with this session's current grant, which is the original one or the last one a refresh returned. */
58
+ retry(): void;
59
+ /** Closes the session and drops pending work. The application calls this explicitly, because an ordinary detach keeps pending work. */
60
+ discard(): void;
61
+ }
62
+ export {};
63
+ //# sourceMappingURL=document_session.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"document_session.d.ts","sourceRoot":"","sources":["../src/document_session.ts"],"names":[],"mappings":"AAUA,OAAO,KAAK,CAAC,MAAM,KAAK,CAAC;AAEzB,OAAO,EAAE,mBAAmB,EAAE,KAAK,aAAa,EAAuB,MAAM,2BAA2B,CAAC;AAEzG,MAAM,WAAW,kBAAkB;IACjC,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,KAAK,EAAE,MAAM,CAAC;IACd,IAAI,EAAE,MAAM,CAAC;IACb,gIAAgI;IAChI,OAAO,CAAC,EAAE,MAAM,CAAC;CAClB;AACD,MAAM,MAAM,kBAAkB,GAAG,QAAQ,CAAC;IAAE,OAAO,EAAE,MAAM,CAAC;IAAC,KAAK,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,MAAM,CAAC;IAAC,OAAO,CAAC,EAAE,MAAM,CAAA;CAAE,CAAC,CAAC;AAG9G,MAAM,MAAM,oBAAoB,GAAG,MAAM,GAAG,SAAS,GAAG,QAAQ,CAAC;AA8BjE,sFAAsF;AACtF,wBAAgB,WAAW,CAAC,UAAU,EAAE,kBAAkB,GAAG,MAAM,CAElE;AAKD,QAAA,MAAM,WAAW,eAAwB,CAAC;AAE1C,QAAA,MAAM,iBAAiB,eAA8B,CAAC;AAEtD,qFAAqF;AACrF,qBAAa,oBAAqB,SAAQ,WAAW;;IAO/B,QAAQ,CAAC,QAAQ,EAAE,aAAa;IANpD,MAAM,CAAC,GAAG,CAAC,QAAQ,EAAE,aAAa,GAAG,oBAAoB;IAMzD,OAAO;IAIP,IAAI,QAAQ,IAAI,SAAS,eAAe,EAAE,CAAyC;IAEnF,0EAA0E;IAC1E,OAAO,CAAC,KAAK,EAAE,kBAAkB,GAAG,aAAa;IAkBjD,CAAC,iBAAiB,CAAC,CAAC,OAAO,EAAE,eAAe,GAAG,IAAI;CAGpD;AAED,kFAAkF;AAClF,qBAAa,aAAa;;IAGZ,QAAQ,CAAC,OAAO,EAAE,eAAe;gBAAxB,OAAO,EAAE,eAAe,EAAE,SAAS,EAAE,MAAM,IAAI;IAGpE,qFAAqF;IACrF,IAAI,MAAM,IAAI,WAAW,CAAoC;IAC7D,WAAW,CAAC,KAAK,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,IAAI,GAAG,IAAI;IAGxD,kFAAkF;IAClF,OAAO,IAAI,IAAI;CAKhB;AAOD,qBAAa,eAAe;;IAcxB,QAAQ,CAAC,KAAK,EAAE,oBAAoB;IACpC,QAAQ,CAAC,UAAU,EAAE,kBAAkB;IACvC,OAAO,CAAC,QAAQ,CAAC,MAAM;IAfzB,QAAQ,CAAC,GAAG,QAAe;IAC3B,QAAQ,CAAC,QAAQ,EAAE,mBAAmB,CAAC;IAUvC,qEAAqE;gBAE1D,KAAK,EAAE,oBAAoB,EAC3B,UAAU,EAAE,kBAAkB,EACtB,MAAM,EAAE,MAAM,IAAI;IAwBrC,IAAI,KAAK,IAAI,OAAO,CAAwB;IAC5C,IAAI,UAAU,IAAI,OAAO,CAAqC;IAC9D,IAAI,UAAU,IAAI,OAAO,CAAC,IAAI,CAAC,CAAqC;IACpE,IAAI,KAAK,IAAI,oBAAoB,CAAgD;IAEjF,CAAC,WAAW,CAAC,IAAI,aAAa;IAa9B,kHAAkH;IAClH,KAAK,IAAI,IAAI;IAGb,uIAAuI;IACvI,OAAO,IAAI,IAAI;CA+EhB"}