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.
- package/README.md +250 -36
- package/dist/actioncable_provider.d.ts +26 -1
- package/dist/actioncable_provider.d.ts.map +1 -1
- package/dist/actioncable_provider.js +295 -150
- package/dist/actioncable_provider.js.map +1 -1
- package/dist/cjs/actioncable_provider.d.ts +26 -1
- package/dist/cjs/actioncable_provider.js +296 -151
- package/dist/cjs/document_element.d.ts +24 -0
- package/dist/cjs/document_element.js +260 -0
- package/dist/cjs/document_session.d.ts +62 -0
- package/dist/cjs/document_session.js +335 -0
- package/dist/cjs/index.d.ts +2 -0
- package/dist/cjs/index.js +4 -1
- package/dist/cjs/reliable_sync.d.ts +18 -31
- package/dist/cjs/reliable_sync.js +128 -98
- package/dist/cjs/turbo_adapter.d.ts +9 -0
- package/dist/cjs/turbo_adapter.js +81 -0
- package/dist/cjs/y_protocol_session.d.ts +7 -14
- package/dist/cjs/y_protocol_session.js +97 -92
- package/dist/document_element.d.ts +25 -0
- package/dist/document_element.d.ts.map +1 -0
- package/dist/document_element.js +225 -0
- package/dist/document_element.js.map +1 -0
- package/dist/document_session.d.ts +63 -0
- package/dist/document_session.d.ts.map +1 -0
- package/dist/document_session.js +296 -0
- package/dist/document_session.js.map +1 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +2 -0
- package/dist/index.js.map +1 -1
- package/dist/reliable_sync.d.ts +18 -31
- package/dist/reliable_sync.d.ts.map +1 -1
- package/dist/reliable_sync.js +128 -98
- package/dist/reliable_sync.js.map +1 -1
- package/dist/turbo_adapter.d.ts +10 -0
- package/dist/turbo_adapter.d.ts.map +1 -0
- package/dist/turbo_adapter.js +78 -0
- package/dist/turbo_adapter.js.map +1 -0
- package/dist/y_protocol_session.d.ts +7 -14
- package/dist/y_protocol_session.d.ts.map +1 -1
- package/dist/y_protocol_session.js +97 -92
- package/dist/y_protocol_session.js.map +1 -1
- package/package.json +29 -6
package/dist/reliable_sync.js
CHANGED
|
@@ -1,139 +1,169 @@
|
|
|
1
|
-
// Transport-agnostic reliable
|
|
2
|
-
// protocol: an ack-tracked queue of unacknowledged local updates,
|
|
3
|
-
// sync-since-last-ack (the unacked tail goes out as one merged, causally-complete
|
|
4
|
-
// delta so the server never sees an internal gap), cumulative acks, periodic
|
|
5
|
-
// retransmit, and reconnect replay.
|
|
1
|
+
// Transport-agnostic reliable delivery for the yrby y-websocket protocol.
|
|
6
2
|
//
|
|
7
|
-
//
|
|
8
|
-
//
|
|
9
|
-
//
|
|
10
|
-
//
|
|
11
|
-
//
|
|
12
|
-
//
|
|
13
|
-
//
|
|
3
|
+
// Each local update gets a sequence number and sits in an ordered queue until
|
|
4
|
+
// the server acknowledges it. While the transport is up, ReliableSync sends the
|
|
5
|
+
// whole unacknowledged tail as one merged, causally complete delta so the
|
|
6
|
+
// server never sees an internal gap, and resends it on a timer until an ack
|
|
7
|
+
// arrives. Acks are cumulative, so one { ack: n } clears everything up to n.
|
|
8
|
+
// While the transport is down, ReliableSync sends nothing and drops nothing,
|
|
9
|
+
// and it replays the tail when the transport reconnects.
|
|
10
|
+
//
|
|
11
|
+
// It doesn't touch the transport or Yjs itself. You inject two functions.
|
|
12
|
+
// send(update, id) transmits one delta with the sequence id the server should
|
|
13
|
+
// acknowledge, and merge(updates) combines update byte arrays into one (usually
|
|
14
|
+
// Y.mergeUpdates). Call enqueue(update) on each local edit, acknowledge(id) when
|
|
15
|
+
// an { ack: id } envelope arrives, resume() when the transport connects, and
|
|
16
|
+
// pause() when it drops.
|
|
14
17
|
//
|
|
15
18
|
// Awareness/presence stays out of scope; it's fire-and-forget in the provider.
|
|
16
|
-
const
|
|
19
|
+
const DEFAULT_RESEND_INTERVAL = 1000;
|
|
17
20
|
export class ReliableSync {
|
|
18
|
-
|
|
19
|
-
pending = [];
|
|
21
|
+
#pending = [];
|
|
20
22
|
#send;
|
|
21
23
|
#merge;
|
|
22
24
|
#resendInterval;
|
|
23
25
|
#setInterval;
|
|
24
26
|
#clearInterval;
|
|
25
27
|
#nextSeq = 1;
|
|
26
|
-
#
|
|
27
|
-
#timer
|
|
28
|
-
//
|
|
29
|
-
//
|
|
30
|
-
|
|
28
|
+
#phase = "paused";
|
|
29
|
+
#timer;
|
|
30
|
+
// Incremented on every queue or phase change. Injected send/merge/timer
|
|
31
|
+
// functions can call back into this object, so code that calls one compares
|
|
32
|
+
// the version afterwards to detect changes made during the call.
|
|
33
|
+
#version = 0;
|
|
34
|
+
// The queue merged into one delta. It's memoized until the queue changes so
|
|
35
|
+
// retransmit ticks don't re-merge the whole queue every second.
|
|
36
|
+
#tail;
|
|
31
37
|
constructor(opts) {
|
|
32
|
-
const { send, merge, resendInterval } = opts ?? {};
|
|
38
|
+
const { send, merge, resendInterval, setInterval: setTimer, clearInterval: clearTimer } = opts ?? {};
|
|
33
39
|
if (typeof send !== "function")
|
|
34
40
|
throw new TypeError("ReliableSync requires a send(update, id) function");
|
|
35
41
|
if (typeof merge !== "function")
|
|
36
42
|
throw new TypeError("ReliableSync requires a merge(updates) function");
|
|
37
|
-
|
|
38
|
-
this.#merge = merge;
|
|
39
|
-
const interval = resendInterval ?? DEFAULTS.resendInterval;
|
|
43
|
+
const interval = resendInterval ?? DEFAULT_RESEND_INTERVAL;
|
|
40
44
|
if (!Number.isFinite(interval) || interval <= 0) {
|
|
41
45
|
throw new TypeError("ReliableSync resendInterval must be a positive number");
|
|
42
46
|
}
|
|
47
|
+
this.#send = send;
|
|
48
|
+
this.#merge = merge;
|
|
43
49
|
this.#resendInterval = interval;
|
|
44
|
-
|
|
45
|
-
this.#
|
|
46
|
-
|
|
50
|
+
this.#setInterval = setTimer ?? ((fn, ms) => setInterval(fn, ms));
|
|
51
|
+
this.#clearInterval = clearTimer ?? ((h) => clearInterval(h));
|
|
52
|
+
}
|
|
53
|
+
/** A snapshot of unacknowledged local updates, oldest first. Editing it does not change the queue. */
|
|
54
|
+
get pending() {
|
|
55
|
+
return this.#pending.map(({ seq, update }) => ({ seq, update: update.slice() }));
|
|
47
56
|
}
|
|
48
57
|
/** True while there are unacknowledged local updates. */
|
|
49
58
|
get hasPending() {
|
|
50
|
-
return this
|
|
59
|
+
return this.#pending.length > 0;
|
|
51
60
|
}
|
|
52
|
-
/**
|
|
53
|
-
* Record a local document update. It is queued and the unacked tail is
|
|
54
|
-
* flushed; the update remains retained until the server acknowledges it.
|
|
55
|
-
*/
|
|
61
|
+
/** Queue a local update and, while connected, send the tail. Ignored after destroy(). */
|
|
56
62
|
enqueue(update) {
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
this
|
|
63
|
+
if (this.#phase === "destroyed")
|
|
64
|
+
return;
|
|
65
|
+
this.#pending.push({ seq: this.#nextSeq++, update: new Uint8Array(update) });
|
|
66
|
+
this.#queueChanged();
|
|
67
|
+
this.#flush();
|
|
62
68
|
}
|
|
63
69
|
/**
|
|
64
|
-
*
|
|
65
|
-
*
|
|
66
|
-
*
|
|
70
|
+
* Confirm delivery through `id`, removing every queued update with
|
|
71
|
+
* seq <= id. Acks come off the wire, so ignore a malformed value or an id
|
|
72
|
+
* beyond anything sent.
|
|
67
73
|
*/
|
|
68
|
-
|
|
69
|
-
if (
|
|
74
|
+
acknowledge(id) {
|
|
75
|
+
if (this.#phase === "destroyed" || !Number.isSafeInteger(id) || id < 0)
|
|
76
|
+
return;
|
|
77
|
+
const newest = this.#pending.at(-1);
|
|
78
|
+
if (newest && id > newest.seq)
|
|
70
79
|
return;
|
|
71
|
-
this.#
|
|
80
|
+
this.#pending = this.#pending.filter((p) => p.seq > id);
|
|
81
|
+
this.#queueChanged();
|
|
72
82
|
}
|
|
73
|
-
/**
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
return; // malformed / impossible
|
|
82
|
-
if (this.pending.length > 0 && id > this.pending[this.pending.length - 1].seq)
|
|
83
|
-
return; // future ack
|
|
84
|
-
this.pending = this.pending.filter((p) => p.seq > id);
|
|
85
|
-
this.#tailCache = undefined; // tail changed
|
|
86
|
-
if (this.pending.length === 0)
|
|
87
|
-
this.#stopTimer();
|
|
83
|
+
/** Call when the transport is up. Replays the tail and keeps retransmitting until it is acknowledged. */
|
|
84
|
+
resume() {
|
|
85
|
+
if (this.#phase === "destroyed")
|
|
86
|
+
return;
|
|
87
|
+
this.#phase = "live";
|
|
88
|
+
this.#version++;
|
|
89
|
+
this.#updateTimer();
|
|
90
|
+
this.#flush();
|
|
88
91
|
}
|
|
89
|
-
/**
|
|
90
|
-
|
|
91
|
-
this.#
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
92
|
+
/** Call when the transport is down. Keeps the queue and stops retransmitting. */
|
|
93
|
+
pause() {
|
|
94
|
+
if (this.#phase === "destroyed")
|
|
95
|
+
return;
|
|
96
|
+
this.#phase = "paused";
|
|
97
|
+
this.#version++;
|
|
98
|
+
this.#updateTimer();
|
|
95
99
|
}
|
|
96
|
-
/**
|
|
97
|
-
|
|
98
|
-
this.#
|
|
99
|
-
this.#stopTimer();
|
|
100
|
+
/** Send the tail again if anything is unacknowledged. The internal timer calls this, and a host with its own scheduler can too. */
|
|
101
|
+
retransmit() {
|
|
102
|
+
this.#flush();
|
|
100
103
|
}
|
|
101
|
-
/**
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
*/
|
|
105
|
-
onTick() {
|
|
106
|
-
if (!this.#connected || this.pending.length === 0)
|
|
104
|
+
/** Stop the timer and drop the queue. Later enqueues are ignored. */
|
|
105
|
+
destroy() {
|
|
106
|
+
if (this.#phase === "destroyed")
|
|
107
107
|
return;
|
|
108
|
-
this
|
|
108
|
+
this.#phase = "destroyed";
|
|
109
|
+
this.#pending = [];
|
|
110
|
+
this.#queueChanged();
|
|
109
111
|
}
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
this.#
|
|
113
|
-
this.#
|
|
114
|
-
this.pending = [];
|
|
115
|
-
this.#tailCache = undefined;
|
|
112
|
+
#queueChanged() {
|
|
113
|
+
this.#version++;
|
|
114
|
+
this.#tail = undefined;
|
|
115
|
+
this.#updateTimer();
|
|
116
116
|
}
|
|
117
|
-
|
|
118
|
-
#
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
117
|
+
// Start or stop the retransmit timer so it runs only while live with work queued.
|
|
118
|
+
#updateTimer() {
|
|
119
|
+
const wanted = this.#phase === "live" && this.hasPending;
|
|
120
|
+
if (wanted === (this.#timer !== undefined))
|
|
121
|
+
return;
|
|
122
|
+
if (!wanted) {
|
|
123
|
+
const timer = this.#timer;
|
|
124
|
+
this.#timer = undefined;
|
|
125
|
+
timer.stop();
|
|
126
|
+
return;
|
|
122
127
|
}
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
128
|
+
// Install the timer before calling setInterval so a tick during that call
|
|
129
|
+
// finds its own timer. Stopping it before the handle exists is a no-op, and
|
|
130
|
+
// the check after setInterval returns cancels the real handle.
|
|
131
|
+
const timer = this.#timer = { stop: () => { } };
|
|
132
|
+
let handle;
|
|
133
|
+
try {
|
|
134
|
+
handle = this.#setInterval(() => { if (this.#timer === timer)
|
|
135
|
+
this.#flush(); }, this.#resendInterval);
|
|
136
|
+
}
|
|
137
|
+
catch (error) {
|
|
138
|
+
// The queue is untouched, and the next resume or queue change tries again.
|
|
139
|
+
if (this.#timer === timer)
|
|
140
|
+
this.#timer = undefined;
|
|
141
|
+
throw error;
|
|
142
|
+
}
|
|
143
|
+
timer.stop = () => this.#clearInterval(handle);
|
|
144
|
+
// An injected timer may tick before returning its handle, and that tick
|
|
145
|
+
// can pause or drain delivery.
|
|
146
|
+
if (this.#timer !== timer) {
|
|
147
|
+
timer.stop();
|
|
127
148
|
return;
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
if (t && typeof t.unref === "function")
|
|
131
|
-
t.unref();
|
|
149
|
+
}
|
|
150
|
+
handle?.unref?.();
|
|
132
151
|
}
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
this.#
|
|
152
|
+
// Send the whole tail as one delta, tagged with its highest seq so one ack
|
|
153
|
+
// covers all of it. Does nothing while disconnected.
|
|
154
|
+
#flush() {
|
|
155
|
+
if (this.#phase !== "live" || !this.#pending.length)
|
|
156
|
+
return;
|
|
157
|
+
if (this.#tail === undefined) {
|
|
158
|
+
const version = this.#version;
|
|
159
|
+
const updates = this.#pending.map((p) => p.update);
|
|
160
|
+
const tail = updates.length === 1 ? updates[0] : this.#merge(updates);
|
|
161
|
+
// merge may have changed the queue, paused, or already sent through a nested resume.
|
|
162
|
+
if (this.#version !== version)
|
|
163
|
+
return;
|
|
164
|
+
this.#tail = tail;
|
|
165
|
+
}
|
|
166
|
+
this.#send(this.#tail, this.#pending.at(-1).seq);
|
|
137
167
|
}
|
|
138
168
|
}
|
|
139
169
|
//# sourceMappingURL=reliable_sync.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"reliable_sync.js","sourceRoot":"","sources":["../src/reliable_sync.ts"],"names":[],"mappings":"AAAA,
|
|
1
|
+
{"version":3,"file":"reliable_sync.js","sourceRoot":"","sources":["../src/reliable_sync.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
export interface DocumentMount {
|
|
2
|
+
readonly ownerDocument: Document;
|
|
3
|
+
readonly isConnected: boolean;
|
|
4
|
+
activate(): void;
|
|
5
|
+
deactivate(): void;
|
|
6
|
+
}
|
|
7
|
+
export declare function registerDocumentMount(mount: DocumentMount): () => void;
|
|
8
|
+
/** Tears down the adapter, for application shutdown or tests. */
|
|
9
|
+
export declare function disconnectTurbo(document: Document): void;
|
|
10
|
+
//# sourceMappingURL=turbo_adapter.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"turbo_adapter.d.ts","sourceRoot":"","sources":["../src/turbo_adapter.ts"],"names":[],"mappings":"AAGA,MAAM,WAAW,aAAa;IAC5B,QAAQ,CAAC,aAAa,EAAE,QAAQ,CAAC;IACjC,QAAQ,CAAC,WAAW,EAAE,OAAO,CAAC;IAC9B,QAAQ,IAAI,IAAI,CAAC;IACjB,UAAU,IAAI,IAAI,CAAC;CACpB;AAOD,wBAAgB,qBAAqB,CAAC,KAAK,EAAE,aAAa,GAAG,MAAM,IAAI,CAStE;AAED,iEAAiE;AACjE,wBAAgB,eAAe,CAAC,QAAQ,EAAE,QAAQ,GAAG,IAAI,CAAuC"}
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
const adapters = new WeakMap();
|
|
2
|
+
// Turbo (Hotwire) and Turbolinks 5 fire the same lifecycle events under different names.
|
|
3
|
+
const CACHE_EVENTS = ["turbo:before-cache", "turbolinks:before-cache"];
|
|
4
|
+
const RENDER_EVENTS = ["turbo:render", "turbo:load", "turbo:fetch-request-error", "turbolinks:render", "turbolinks:load"];
|
|
5
|
+
const PREVIEW_ATTRIBUTES = ["data-turbo-preview", "data-turbolinks-preview"];
|
|
6
|
+
export function registerDocumentMount(mount) {
|
|
7
|
+
let adapter = adapters.get(mount.ownerDocument);
|
|
8
|
+
if (!adapter)
|
|
9
|
+
adapters.set(mount.ownerDocument, adapter = new TurboAdapter(mount.ownerDocument));
|
|
10
|
+
adapter.mounts.add(mount);
|
|
11
|
+
adapter.reconcile();
|
|
12
|
+
return () => {
|
|
13
|
+
adapter.mounts.delete(mount);
|
|
14
|
+
if (!adapter.mounts.size)
|
|
15
|
+
adapter.destroy();
|
|
16
|
+
};
|
|
17
|
+
}
|
|
18
|
+
/** Tears down the adapter, for application shutdown or tests. */
|
|
19
|
+
export function disconnectTurbo(document) { adapters.get(document)?.destroy(); }
|
|
20
|
+
class TurboAdapter {
|
|
21
|
+
document;
|
|
22
|
+
mounts = new Set();
|
|
23
|
+
#state = { phase: "active" };
|
|
24
|
+
constructor(document) {
|
|
25
|
+
this.document = document;
|
|
26
|
+
for (const event of CACHE_EVENTS)
|
|
27
|
+
document.addEventListener(event, this.#beforeCache);
|
|
28
|
+
for (const event of RENDER_EVENTS)
|
|
29
|
+
document.addEventListener(event, this.reconcile);
|
|
30
|
+
}
|
|
31
|
+
reconcile = () => {
|
|
32
|
+
if (this.#state.phase !== "active")
|
|
33
|
+
return;
|
|
34
|
+
const html = this.document.documentElement;
|
|
35
|
+
const preview = PREVIEW_ATTRIBUTES.some(attribute => html?.hasAttribute(attribute));
|
|
36
|
+
for (const mount of this.mounts) {
|
|
37
|
+
if (!mount.isConnected || preview)
|
|
38
|
+
mount.deactivate();
|
|
39
|
+
else
|
|
40
|
+
mount.activate();
|
|
41
|
+
}
|
|
42
|
+
};
|
|
43
|
+
#beforeCache = () => {
|
|
44
|
+
const state = this.#state;
|
|
45
|
+
if (state.phase !== "active")
|
|
46
|
+
return;
|
|
47
|
+
for (const mount of this.mounts)
|
|
48
|
+
mount.deactivate();
|
|
49
|
+
if (this.#state !== state)
|
|
50
|
+
return;
|
|
51
|
+
// A canceled or failed navigation fires no render or load event, so we
|
|
52
|
+
// reconcile ourselves after Turbo finishes cloning the snapshot. The clone
|
|
53
|
+
// takes two turns, which is why the timers are nested.
|
|
54
|
+
clearTimeout(state.timer);
|
|
55
|
+
state.timer = setTimeout(() => {
|
|
56
|
+
if (this.#state === state)
|
|
57
|
+
state.timer = setTimeout(this.reconcile, 0);
|
|
58
|
+
}, 0);
|
|
59
|
+
};
|
|
60
|
+
destroy() {
|
|
61
|
+
const state = this.#state;
|
|
62
|
+
if (state.phase === "destroyed")
|
|
63
|
+
return;
|
|
64
|
+
this.#state = { phase: "destroyed" };
|
|
65
|
+
clearTimeout(state.timer);
|
|
66
|
+
// Leave the registry first so cleanup callbacks can register a replacement
|
|
67
|
+
// adapter for this document.
|
|
68
|
+
adapters.delete(this.document);
|
|
69
|
+
for (const event of CACHE_EVENTS)
|
|
70
|
+
this.document.removeEventListener(event, this.#beforeCache);
|
|
71
|
+
for (const event of RENDER_EVENTS)
|
|
72
|
+
this.document.removeEventListener(event, this.reconcile);
|
|
73
|
+
for (const mount of this.mounts)
|
|
74
|
+
mount.deactivate();
|
|
75
|
+
this.mounts.clear();
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
//# sourceMappingURL=turbo_adapter.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"turbo_adapter.js","sourceRoot":"","sources":["../src/turbo_adapter.ts"],"names":[],"mappings":"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"}
|
|
@@ -37,10 +37,10 @@ export declare class YProtocolSession {
|
|
|
37
37
|
get synced(): boolean;
|
|
38
38
|
/** True while there are unacknowledged local document updates in flight. */
|
|
39
39
|
get hasPending(): boolean;
|
|
40
|
-
/**
|
|
41
|
-
|
|
42
|
-
/**
|
|
43
|
-
|
|
40
|
+
/** Call when the transport is up. Sends the opening handshake, re-announces presence, and replays the unacked tail. */
|
|
41
|
+
resume(): void;
|
|
42
|
+
/** Call when the transport is down. Keeps the queue, stops retransmits, and clears peers' presence. */
|
|
43
|
+
pause(): void;
|
|
44
44
|
/**
|
|
45
45
|
* Broadcast that our local presence is gone (sets local state to null, which
|
|
46
46
|
* emits a removal awareness frame through `send`). Call this while the
|
|
@@ -48,15 +48,8 @@ export declare class YProtocolSession {
|
|
|
48
48
|
* waiting for the awareness timeout. A no-op when there's no local state.
|
|
49
49
|
*/
|
|
50
50
|
removeLocalAwareness(): void;
|
|
51
|
-
/**
|
|
52
|
-
|
|
53
|
-
* the server settled the update WITHOUT recording it (rejected as an
|
|
54
|
-
* unhealable causal gap after repeated resyncs). The queue is pruned either
|
|
55
|
-
* way — retransmitting an unhealable update would loop forever — but a
|
|
56
|
-
* dropped settle is surfaced via onError ("ack-dropped") so the app can warn
|
|
57
|
-
* or reload instead of silently reporting synced over lost data.
|
|
58
|
-
*/
|
|
59
|
-
ack(id: number, dropped?: boolean): void;
|
|
51
|
+
/** A reliable-delivery `{ ack: id }` envelope arrived. */
|
|
52
|
+
acknowledge(id: number): void;
|
|
60
53
|
/**
|
|
61
54
|
* Apply an update without treating it as a local edit, so it isn't queued for
|
|
62
55
|
* re-delivery to the server. Use it for bootstrap/restore: initial state loaded
|
|
@@ -67,7 +60,7 @@ export declare class YProtocolSession {
|
|
|
67
60
|
* keystroke becomes an outbound frame), so a bare `Y.applyUpdate(doc, update)`
|
|
68
61
|
* would look like a local edit and get echoed back on the next connect. Going
|
|
69
62
|
* through here applies under the session's own origin, which the outbound
|
|
70
|
-
* filter skips. Safe to call before `
|
|
63
|
+
* filter skips. Safe to call before `resume()`: the state folds into the
|
|
71
64
|
* SyncStep1 handshake instead of being re-sent.
|
|
72
65
|
*/
|
|
73
66
|
applyRemoteUpdate(update: Uint8Array): void;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"y_protocol_session.d.ts","sourceRoot":"","sources":["../src/y_protocol_session.ts"],"names":[],"mappings":"AAUA,OAAO,EAAE,GAAG,EAA6B,MAAM,KAAK,CAAC;AAIrD,OAAO,EAIL,KAAK,SAAS,EACf,MAAM,uBAAuB,CAAC;AAC/B,OAAO,EAAgB,KAAK,WAAW,EAAE,MAAM,oBAAoB,CAAC;AAIpE,eAAO,MAAM,WAAW;;;CAAqC,CAAC;AAE9D,MAAM,WAAW,uBAAuB;IACtC;;;;;OAKG;IACH,IAAI,EAAE,CAAC,KAAK,EAAE,UAAU,EAAE,EAAE,EAAE,MAAM,GAAG,SAAS,KAAK,IAAI,CAAC;IAC1D,+EAA+E;IAC/E,SAAS,CAAC,EAAE,SAAS,GAAG,IAAI,CAAC;IAC7B,iCAAiC;IACjC,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB;;;;;OAKG;IACH,OAAO,CAAC,EAAE,CAAC,KAAK,EAAE,OAAO,EAAE,OAAO,EAAE,MAAM,KAAK,IAAI,CAAC;IACpD,2EAA2E;IAC3E,WAAW,CAAC,EAAE,CAAC,OAAO,EAAE,MAAM,IAAI,EAAE,EAAE,EAAE,MAAM,KAAK,WAAW,CAAC;IAC/D,aAAa,CAAC,EAAE,CAAC,MAAM,EAAE,WAAW,KAAK,IAAI,CAAC;CAC/C;
|
|
1
|
+
{"version":3,"file":"y_protocol_session.d.ts","sourceRoot":"","sources":["../src/y_protocol_session.ts"],"names":[],"mappings":"AAUA,OAAO,EAAE,GAAG,EAA6B,MAAM,KAAK,CAAC;AAIrD,OAAO,EAIL,KAAK,SAAS,EACf,MAAM,uBAAuB,CAAC;AAC/B,OAAO,EAAgB,KAAK,WAAW,EAAE,MAAM,oBAAoB,CAAC;AAIpE,eAAO,MAAM,WAAW;;;CAAqC,CAAC;AAE9D,MAAM,WAAW,uBAAuB;IACtC;;;;;OAKG;IACH,IAAI,EAAE,CAAC,KAAK,EAAE,UAAU,EAAE,EAAE,EAAE,MAAM,GAAG,SAAS,KAAK,IAAI,CAAC;IAC1D,+EAA+E;IAC/E,SAAS,CAAC,EAAE,SAAS,GAAG,IAAI,CAAC;IAC7B,iCAAiC;IACjC,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB;;;;;OAKG;IACH,OAAO,CAAC,EAAE,CAAC,KAAK,EAAE,OAAO,EAAE,OAAO,EAAE,MAAM,KAAK,IAAI,CAAC;IACpD,2EAA2E;IAC3E,WAAW,CAAC,EAAE,CAAC,OAAO,EAAE,MAAM,IAAI,EAAE,EAAE,EAAE,MAAM,KAAK,WAAW,CAAC;IAC/D,aAAa,CAAC,EAAE,CAAC,MAAM,EAAE,WAAW,KAAK,IAAI,CAAC;CAC/C;AAUD,qBAAa,gBAAgB;;IAC3B,QAAQ,CAAC,GAAG,EAAE,GAAG,CAAC;IAClB,QAAQ,CAAC,SAAS,EAAE,SAAS,GAAG,IAAI,CAAC;gBASzB,GAAG,EAAE,GAAG,EAAE,IAAI,EAAE,uBAAuB;IAuCnD,mFAAmF;IACnF,IAAI,MAAM,IAAI,OAAO,CAEpB;IAED,4EAA4E;IAC5E,IAAI,UAAU,IAAI,OAAO,CAExB;IAED,uHAAuH;IACvH,MAAM,IAAI,IAAI;IAYd,uGAAuG;IACvG,KAAK,IAAI,IAAI;IAUb;;;;;OAKG;IACH,oBAAoB,IAAI,IAAI;IAM5B,0DAA0D;IAC1D,WAAW,CAAC,EAAE,EAAE,MAAM,GAAG,IAAI;IAI7B;;;;;;;;;;;;OAYG;IACH,iBAAiB,CAAC,MAAM,EAAE,UAAU,GAAG,IAAI;IAI3C;;;;OAIG;IACH,OAAO,CAAC,KAAK,EAAE,UAAU,GAAG,UAAU,GAAG,IAAI;IAmC7C,2DAA2D;IAC3D,OAAO,IAAI,IAAI;CA+BhB"}
|