edytor 3.0.0 → 5.0.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 +2 -2
- package/dist/block/block.svelte.js +6 -21
- package/dist/block/inlineBlock.svelte.js +4 -6
- package/dist/clipboard/moveText.js +6 -8
- package/dist/cloudflare/DocumentRoom.d.ts +23 -12
- package/dist/cloudflare/DocumentRoom.js +10 -6
- package/dist/cloudflare/index.d.ts +1 -1
- package/dist/cloudflare/room/access.d.ts +8 -3
- package/dist/cloudflare/room/access.js +44 -20
- package/dist/cloudflare/room/admission.js +28 -5
- package/dist/cloudflare/room/comments.js +28 -1
- package/dist/cloudflare/room/context.d.ts +139 -129
- package/dist/cloudflare/room/context.js +11 -4
- package/dist/cloudflare/room/forged.d.ts +14 -0
- package/dist/cloudflare/room/forged.js +30 -2
- package/dist/cloudflare/room/frames.d.ts +4 -0
- package/dist/cloudflare/room/frames.js +5 -1
- package/dist/cloudflare/room/purge.d.ts +6 -3
- package/dist/cloudflare/room/purge.js +45 -19
- package/dist/cloudflare/room/replicas.d.ts +4 -2
- package/dist/cloudflare/room/replicas.js +8 -3
- package/dist/cloudflare/room/suggest.d.ts +7 -0
- package/dist/cloudflare/room/suggest.js +143 -0
- package/dist/cloudflare/room/updates.d.ts +3 -4
- package/dist/cloudflare/room/updates.js +6 -8
- package/dist/cloudflare/routeDocumentSocket.d.ts +10 -1
- package/dist/cloudflare/routeDocumentSocket.js +5 -3
- package/dist/components/Content.svelte +9 -1
- package/dist/crdt/anchors.js +3 -2
- package/dist/crdt/attribution/attribution.d.ts +34 -12
- package/dist/crdt/attribution/attribution.js +59 -21
- package/dist/crdt/attribution/block.d.ts +3 -3
- package/dist/crdt/attribution/block.js +7 -8
- package/dist/crdt/attribution/index.d.ts +1 -1
- package/dist/crdt/attribution/index.js +1 -1
- package/dist/crdt/data.d.ts +62 -31
- package/dist/crdt/data.js +211 -103
- package/dist/crdt/doc/capability.js +9 -13
- package/dist/crdt/doc/delete.d.ts +1 -1
- package/dist/crdt/doc/delete.js +13 -30
- package/dist/crdt/doc/funnel.d.ts +1 -1
- package/dist/crdt/doc/funnel.js +34 -26
- package/dist/crdt/doc/gate.d.ts +3 -3
- package/dist/crdt/doc/history.d.ts +31 -9
- package/dist/crdt/doc/history.js +204 -73
- package/dist/crdt/doc/layout.js +16 -27
- package/dist/crdt/doc/meta.js +17 -23
- package/dist/crdt/doc/moves.d.ts +1 -1
- package/dist/crdt/doc/moves.js +52 -32
- package/dist/crdt/doc/reads.d.ts +2 -2
- package/dist/crdt/doc/reads.js +20 -30
- package/dist/crdt/doc/seed.js +2 -2
- package/dist/crdt/doc/split-merge.js +12 -15
- package/dist/crdt/doc/steps.d.ts +25 -13
- package/dist/crdt/doc/steps.js +55 -125
- package/dist/crdt/doc/suggest.d.ts +167 -0
- package/dist/crdt/doc/suggest.js +579 -0
- package/dist/crdt/doc/types.d.ts +21 -9
- package/dist/crdt/document.d.ts +26 -6
- package/dist/crdt/document.js +40 -15
- package/dist/crdt/edytor-doc.d.ts +410 -390
- package/dist/crdt/edytor-doc.js +143 -95
- package/dist/crdt/engine-api.d.ts +39 -1
- package/dist/crdt/engine.d.ts +2 -2
- package/dist/crdt/engine.js +4 -5
- package/dist/crdt/flow.d.ts +2 -2
- package/dist/crdt/flow.js +29 -37
- package/dist/crdt/index.d.ts +134 -127
- package/dist/crdt/index.js +4 -1
- package/dist/crdt/operations.d.ts +12 -3
- package/dist/crdt/parts.d.ts +43 -0
- package/dist/crdt/parts.js +44 -0
- package/dist/crdt/placement/display.d.ts +6 -31
- package/dist/crdt/placement/display.js +41 -146
- package/dist/crdt/placement/keys.d.ts +63 -0
- package/dist/crdt/placement/keys.js +95 -0
- package/dist/crdt/placement/model.d.ts +119 -118
- package/dist/crdt/placement/model.js +242 -142
- package/dist/crdt/placement/resolve.d.ts +123 -35
- package/dist/crdt/placement/resolve.js +188 -153
- package/dist/crdt/placement/slots.d.ts +134 -0
- package/dist/crdt/placement/slots.js +152 -0
- package/dist/crdt/protocol.d.ts +1 -1
- package/dist/crdt/protocol.js +1 -1
- package/dist/crdt/protocols/auth.d.ts +11 -1
- package/dist/crdt/protocols/auth.js +17 -1
- package/dist/crdt/protocols/comments.d.ts +16 -1
- package/dist/crdt/protocols/comments.js +9 -1
- package/dist/crdt/providers/index.d.ts +6 -4
- package/dist/crdt/providers/index.js +3 -1
- package/dist/crdt/providers/room.d.ts +16 -2
- package/dist/crdt/providers/room.js +64 -17
- package/dist/crdt/providers/websocket.d.ts +6 -4
- package/dist/crdt/providers/websocket.js +14 -7
- package/dist/crdt/purge.d.ts +24 -9
- package/dist/crdt/purge.js +266 -126
- package/dist/crdt/restore.d.ts +4 -3
- package/dist/crdt/restore.js +27 -34
- package/dist/crdt/schema.d.ts +30 -21
- package/dist/crdt/schema.js +39 -23
- package/dist/crdt/storage.d.ts +1 -1
- package/dist/crdt/storage.js +1 -1
- package/dist/crdt/structs.d.ts +17 -15
- package/dist/crdt/structs.js +10 -22
- package/dist/crdt/text/index/cache.js +4 -0
- package/dist/crdt/text/index/claims.js +13 -23
- package/dist/crdt/text/index/fold.d.ts +2 -3
- package/dist/crdt/text/index/fold.js +194 -44
- package/dist/crdt/text/index/layout.d.ts +2 -4
- package/dist/crdt/text/index/layout.js +14 -30
- package/dist/crdt/text/index/placement.d.ts +26 -18
- package/dist/crdt/text/index/placement.js +420 -157
- package/dist/crdt/text/index/records.d.ts +3 -3
- package/dist/crdt/text/index/records.js +24 -31
- package/dist/crdt/text/index/shared.d.ts +11 -3
- package/dist/crdt/text/index/shared.js +29 -17
- package/dist/crdt/text/index/state.d.ts +21 -17
- package/dist/crdt/text/index/state.js +15 -17
- package/dist/crdt/text/index/streams.d.ts +10 -5
- package/dist/crdt/text/index/streams.js +78 -27
- package/dist/crdt/text/items.d.ts +10 -3
- package/dist/crdt/text/items.js +7 -0
- package/dist/crdt/text/model.d.ts +87 -62
- package/dist/crdt/text/model.js +91 -169
- package/dist/crdt/text/ranges.d.ts +1 -1
- package/dist/crdt/text/ranges.js +3 -3
- package/dist/crdt/text/rows.d.ts +13 -7
- package/dist/crdt/text/rows.js +24 -7
- package/dist/crdt/text/runs.d.ts +51 -42
- package/dist/crdt/text/runs.js +24 -7
- package/dist/crdt/text/streams.d.ts +26 -13
- package/dist/crdt/text/streams.js +41 -14
- package/dist/crdt/vendor/yjs/UPSTREAM.md +651 -21
- package/dist/crdt/vendor/yjs/dts/global.d.ts +2 -0
- package/dist/crdt/vendor/yjs/dts/index.d.ts +1 -1
- package/dist/crdt/vendor/yjs/dts/structs/AbstractStruct.d.ts +7 -0
- package/dist/crdt/vendor/yjs/dts/structs/Item.d.ts +197 -10
- package/dist/crdt/vendor/yjs/dts/utils/Doc.d.ts +6 -3
- package/dist/crdt/vendor/yjs/dts/utils/StructStore.d.ts +5 -18
- package/dist/crdt/vendor/yjs/dts/utils/Transaction.d.ts +15 -1
- package/dist/crdt/vendor/yjs/dts/utils/UpdateDecoder.d.ts +14 -6
- package/dist/crdt/vendor/yjs/dts/utils/UpdateEncoder.d.ts +33 -6
- package/dist/crdt/vendor/yjs/dts/utils/ids.d.ts +1 -65
- package/dist/crdt/vendor/yjs/dts/utils/marks.d.ts +6 -1
- package/dist/crdt/vendor/yjs/dts/ynode.d.ts +54 -7
- package/dist/crdt/vendor/yjs/global.d.ts +2 -0
- package/dist/crdt/vendor/yjs/src/index.js +1 -1
- package/dist/crdt/vendor/yjs/src/structs/AbstractStruct.js +10 -0
- package/dist/crdt/vendor/yjs/src/structs/Item.js +649 -51
- package/dist/crdt/vendor/yjs/src/utils/BlockSet.js +18 -9
- package/dist/crdt/vendor/yjs/src/utils/Doc.js +5 -3
- package/dist/crdt/vendor/yjs/src/utils/StructStore.js +5 -0
- package/dist/crdt/vendor/yjs/src/utils/Transaction.js +36 -13
- package/dist/crdt/vendor/yjs/src/utils/UpdateDecoder.js +88 -18
- package/dist/crdt/vendor/yjs/src/utils/UpdateEncoder.js +119 -32
- package/dist/crdt/vendor/yjs/src/utils/encoding.js +13 -1
- package/dist/crdt/vendor/yjs/src/utils/ids.js +7 -7
- package/dist/crdt/vendor/yjs/src/utils/marks.js +193 -8
- package/dist/crdt/vendor/yjs/src/utils/redo.js +1 -1
- package/dist/crdt/vendor/yjs/src/utils/transaction-helpers.js +1 -1
- package/dist/crdt/vendor/yjs/src/utils/updates.js +3 -3
- package/dist/crdt/vendor/yjs/src/ynode.js +199 -12
- package/dist/edytor.svelte.d.ts +1 -1
- package/dist/edytor.svelte.js +103 -75
- package/dist/events/beforeInputCommands.d.ts +2 -2
- package/dist/events/beforeInputCommands.js +2 -2
- package/dist/events/nativeInteractiveControl.d.ts +11 -1
- package/dist/events/nativeInteractiveControl.js +41 -1
- package/dist/events/onBeforeInput.js +3 -3
- package/dist/events/onFocus.js +9 -11
- package/dist/events/onPageHide.d.ts +3 -0
- package/dist/events/onPageHide.js +16 -0
- package/dist/export/forms.d.ts +50 -50
- package/dist/index.d.ts +4 -2
- package/dist/index.js +1 -1
- package/dist/labels.d.ts +8 -0
- package/dist/labels.js +6 -1
- package/dist/plugins/comments/CommentsController.svelte.d.ts +5 -1
- package/dist/plugins/comments/CommentsController.svelte.js +14 -4
- package/dist/plugins/find/search.js +2 -2
- package/dist/plugins/index.d.ts +1 -0
- package/dist/plugins/index.js +1 -0
- package/dist/plugins/suggestions/SuggestionBars.svelte +40 -23
- package/dist/plugins/suggestions/SuggestionBars.svelte.d.ts +3 -1
- package/dist/plugins/suggestions/SuggestionRuns.svelte +77 -0
- package/dist/plugins/suggestions/SuggestionRuns.svelte.d.ts +9 -0
- package/dist/plugins/suggestions/shared.svelte.d.ts +58 -0
- package/dist/plugins/suggestions/shared.svelte.js +421 -0
- package/dist/plugins/suggestions/suggestionsPlugin.d.ts +15 -4
- package/dist/selection/domSelection.d.ts +2 -0
- package/dist/selection/domSelection.js +6 -2
- package/dist/selection/pointer.svelte.js +75 -48
- package/dist/selection/replaceSelection.js +2 -6
- package/dist/selection/selection.svelte.d.ts +3 -3
- package/dist/selection/selection.svelte.js +39 -32
- package/dist/selection/selection.utils.d.ts +14 -0
- package/dist/selection/selection.utils.js +28 -3
- package/dist/session/commands.js +21 -11
- package/dist/session/composition.svelte.d.ts +5 -3
- package/dist/session/composition.svelte.js +21 -17
- package/dist/session/handles.d.ts +4 -17
- package/dist/session/handles.js +11 -37
- package/dist/session/navigation.js +6 -9
- package/dist/session/selection.d.ts +11 -5
- package/dist/session/selection.js +14 -48
- package/dist/session/suggestions.svelte.d.ts +38 -9
- package/dist/session/suggestions.svelte.js +37 -18
- package/dist/session/virtual.d.ts +1 -1
- package/dist/surface/attributes.d.ts +5 -0
- package/dist/surface/attributes.js +2 -2
- package/dist/surface/cells.d.ts +2 -26
- package/dist/surface/cells.js +2 -33
- package/dist/surface/observer.svelte.d.ts +6 -1
- package/dist/surface/observer.svelte.js +130 -83
- package/dist/surface/pin.svelte.d.ts +9 -9
- package/dist/surface/pin.svelte.js +15 -22
- package/dist/surface/projector.svelte.d.ts +15 -3
- package/dist/surface/projector.svelte.js +67 -22
- package/dist/text/text.svelte.js +14 -19
- package/dist/themes/notion.css +29 -0
- package/package.json +4 -2
- package/dist/crdt/placement/rank.d.ts +0 -87
- package/dist/crdt/placement/rank.js +0 -332
- package/dist/crdt/placement/source.d.ts +0 -43
- package/dist/crdt/placement/source.js +0 -66
- package/dist/crdt/text/delete-index.d.ts +0 -129
- package/dist/crdt/text/delete-index.js +0 -365
- package/dist/crdt/text/deletes.d.ts +0 -76
- package/dist/crdt/text/deletes.js +0 -362
- package/dist/crdt/text/index/anchored.d.ts +0 -17
- package/dist/crdt/text/index/anchored.js +0 -197
|
@@ -0,0 +1,134 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Slots and move records — where a block or an array item stands (fork
|
|
3
|
+
* patch YP22).
|
|
4
|
+
*
|
|
5
|
+
* Every data owner keeps ONE order sequence, its node's list part (a block's
|
|
6
|
+
* node, an atom's node, the registry root for the top-level blocks, the
|
|
7
|
+
* `docdata` root for the document's data). The sequence holds one countable
|
|
8
|
+
* unit per thing it orders:
|
|
9
|
+
*
|
|
10
|
+
* - a block's **home slot** `{ b: id }`, written once at the block's creation
|
|
11
|
+
* in its parent's sequence, right after its left sibling's unit (YATA's
|
|
12
|
+
* origin rule: later inserts there stay together, `order.insert.run`);
|
|
13
|
+
* - an array item's **slot** `{ p: path, i: id }` (`crdt/data.ts`): the
|
|
14
|
+
* item exists while its slot does;
|
|
15
|
+
* - a **move record** (the engine's `ContentMove`): one unit at the
|
|
16
|
+
* destination naming the units it moves, in order. Each named unit shows
|
|
17
|
+
* at the record that wins its movers — the greatest `(priority, client,
|
|
18
|
+
* clock)` — and reads as deleted at home, so a sequence walk sees it gone
|
|
19
|
+
* from there. Deleting a record (an undo) hands its units to the next
|
|
20
|
+
* record, else home.
|
|
21
|
+
*
|
|
22
|
+
* A reader of a sequence (`entriesOf`) walks its live items: a slot at home,
|
|
23
|
+
* or a record expanded to the units it shows. A writer places a new slot or a
|
|
24
|
+
* record after a displayed entry (`placeAfter`): when that entry is a unit in
|
|
25
|
+
* the middle of a record's display, the new record also carries the record's
|
|
26
|
+
* later units, so the new thing shows between them.
|
|
27
|
+
*
|
|
28
|
+
* The engine is read through `engine.js` (structural types, `engine-api.ts`).
|
|
29
|
+
*/
|
|
30
|
+
import type { EngineDoc, EngineNode } from '../engine-api.js';
|
|
31
|
+
import { type StoreStruct } from '../structs.js';
|
|
32
|
+
export type BlockId = string;
|
|
33
|
+
/**
|
|
34
|
+
* A block's home slot in its parent's sequence; `s`, when the block was made
|
|
35
|
+
* by a split (Enter, a paste of lines): the id (`client, clock`) of the
|
|
36
|
+
* split's boundary item in the text, the engine's sort key among the slots
|
|
37
|
+
* of one origin (the boundaries' order in the text, so two peers' splits of
|
|
38
|
+
* one block keep its text's order, `order.split.text`).
|
|
39
|
+
*/
|
|
40
|
+
export type BlockSlot = {
|
|
41
|
+
b: BlockId;
|
|
42
|
+
s?: [client: number, clock: number];
|
|
43
|
+
};
|
|
44
|
+
/** An array item's slot: the array's path (`crdt/data.ts`) and the item's id. */
|
|
45
|
+
export type DataSlot = {
|
|
46
|
+
p: string;
|
|
47
|
+
i: string;
|
|
48
|
+
};
|
|
49
|
+
/** One unit of a sequence: its engine id. */
|
|
50
|
+
export type Unit = {
|
|
51
|
+
c: number;
|
|
52
|
+
k: number;
|
|
53
|
+
};
|
|
54
|
+
export declare const isBlockSlot: (v: unknown) => v is BlockSlot;
|
|
55
|
+
export declare const isDataSlot: (v: unknown) => v is DataSlot;
|
|
56
|
+
/** A sequence item as the slot reads walk it. */
|
|
57
|
+
export type SeqItem = StoreStruct & {
|
|
58
|
+
left: SeqItem | null;
|
|
59
|
+
right: SeqItem | null;
|
|
60
|
+
content: {
|
|
61
|
+
arr?: unknown[];
|
|
62
|
+
priority?: number;
|
|
63
|
+
side?: number;
|
|
64
|
+
part?: number;
|
|
65
|
+
at?: unknown;
|
|
66
|
+
ids?: number[];
|
|
67
|
+
};
|
|
68
|
+
/** The record that shows this unit (fork patch YP22), `null` at home. */
|
|
69
|
+
mover?: SeqItem | null;
|
|
70
|
+
movers?: SeqItem[] | null;
|
|
71
|
+
/** Removed or held: shown nowhere. */
|
|
72
|
+
gone?: boolean;
|
|
73
|
+
};
|
|
74
|
+
/** Whether `it` is a move record. */
|
|
75
|
+
export declare const isRecord: (it: SeqItem) => boolean;
|
|
76
|
+
/** A record's stamp, the order its wins follow: `(priority, client, clock)`. */
|
|
77
|
+
export type Stamp = readonly [priority: number, client: number, clock: number];
|
|
78
|
+
export declare const stampOf: (r: SeqItem) => Stamp;
|
|
79
|
+
export declare const cmpStamp: (a: Stamp, b: Stamp) => number;
|
|
80
|
+
/** One displayed entry of a sequence: its value, its unit, and the record it shows through (`null`: at home; `at`: its place among the units read with it). */
|
|
81
|
+
export type Entry = {
|
|
82
|
+
value: unknown;
|
|
83
|
+
unit: Unit;
|
|
84
|
+
via: SeqItem | null;
|
|
85
|
+
at: number;
|
|
86
|
+
};
|
|
87
|
+
/** Whether `a` and `b` are one unit. */
|
|
88
|
+
export declare const sameUnit: (a: Unit, b: Unit) => boolean;
|
|
89
|
+
/** The item holding unit `u`, or `null` when `doc` lacks it (no value read: the fold's hot path). */
|
|
90
|
+
export declare const itemAt: (doc: EngineDoc, u: Unit) => SeqItem | null;
|
|
91
|
+
/** The item holding unit `u` and the value at it, or `null` when `doc` lacks it (a GC struct, not arrived). */
|
|
92
|
+
export declare const unitOf: (doc: EngineDoc, u: Unit) => {
|
|
93
|
+
item: SeqItem;
|
|
94
|
+
value: unknown;
|
|
95
|
+
} | null;
|
|
96
|
+
/**
|
|
97
|
+
* The units item `it` can show, in order, those `keep` takes: a live
|
|
98
|
+
* record's named units (present, not gone; `via` the record), a slot item's
|
|
99
|
+
* units, moved away or not (none when it is gone; `via` `null`). THE walk
|
|
100
|
+
* every reader of a sequence's units takes: the engine's (`shownBy`), the
|
|
101
|
+
* index's places (`placement/resolve.ts`) and the writers' view of them
|
|
102
|
+
* (`placement/model.ts`).
|
|
103
|
+
*/
|
|
104
|
+
export declare const unitsOf: (doc: EngineDoc, it: SeqItem, keep: (item: SeqItem, value: unknown, unit: Unit) => boolean) => Entry[];
|
|
105
|
+
/** The units the engine shows at item `it`, in order: a live record's, those it wins; a live slot item's, all. */
|
|
106
|
+
export declare const shownBy: (doc: EngineDoc, it: SeqItem) => Entry[];
|
|
107
|
+
/** The displayed entries of `node`'s sequence, in order. */
|
|
108
|
+
export declare const entriesOf: (doc: EngineDoc, node: EngineNode) => Entry[];
|
|
109
|
+
/**
|
|
110
|
+
* The entry unit `home` displays as: at home when no live record wins it,
|
|
111
|
+
* else at its mover (`via`, `at`); `null` when it shows nowhere (gone).
|
|
112
|
+
*/
|
|
113
|
+
export declare const anchorOf: (doc: EngineDoc, home: Unit) => Entry | null;
|
|
114
|
+
/** The sequence node that holds `e`'s displaying item (its record's, or its slot's). */
|
|
115
|
+
export declare const nodeOf: (doc: EngineDoc, e: Entry) => EngineNode;
|
|
116
|
+
/** `units` as `client, clock, len` triples (one unit each). */
|
|
117
|
+
export declare const triples: (units: readonly Unit[]) => number[];
|
|
118
|
+
/**
|
|
119
|
+
* Write new slots (`payloads`) and/or a move of `moved` (unit triples) into
|
|
120
|
+
* `node`'s sequence right after the displayed entry `left` (at the start when
|
|
121
|
+
* `null`; `right` the entry that follows, if any). When `left` is a unit in
|
|
122
|
+
* the middle of a record's display, the record written here carries that
|
|
123
|
+
* record's later units too, so the new entries show between them. `side` is
|
|
124
|
+
* the record's class among the items of its origin (`0` after, `1` before);
|
|
125
|
+
* `chain` the units the record stands at for the engine's sort of two
|
|
126
|
+
* records at one origin, given its first unit — the home slots from the root
|
|
127
|
+
* sequence down to the unit it stands at (`null`: the first unit alone) — and
|
|
128
|
+
* `part` its order among the records standing there. `shown` reads the units
|
|
129
|
+
* a record shows, as the reader sees them (the index's: a unit a cycle sent
|
|
130
|
+
* back elsewhere is not carried).
|
|
131
|
+
*/
|
|
132
|
+
export declare const placeAfter: (doc: EngineDoc, node: EngineNode, left: Entry | null, right: Entry | null, payloads: readonly unknown[] | null, moved?: readonly number[] | null, side?: number, part?: number, chain?: ((first: Unit) => readonly Unit[]) | null, shown?: (r: SeqItem) => Entry[]) => void;
|
|
133
|
+
/** Remove unit `u` from its sequence (the engine's delete: it shows nowhere any more). */
|
|
134
|
+
export declare const removeUnit: (doc: EngineDoc, u: Unit) => void;
|
|
@@ -0,0 +1,152 @@
|
|
|
1
|
+
import { Y } from '../engine.js';
|
|
2
|
+
import { asYDoc, clientsOf, engineOps, firstItem, storeOf, structAt } from '../structs.js';
|
|
3
|
+
export const isBlockSlot = (v) => v != null && typeof v === 'object' && typeof v.b === 'string';
|
|
4
|
+
export const isDataSlot = (v) => v != null &&
|
|
5
|
+
typeof v === 'object' &&
|
|
6
|
+
typeof v.p === 'string' &&
|
|
7
|
+
typeof v.i === 'string';
|
|
8
|
+
/** Whether `it` is a move record. */
|
|
9
|
+
export const isRecord = (it) => it.content.priority !== undefined && it.content.ids !== undefined;
|
|
10
|
+
export const stampOf = (r) => [r.content.priority, r.id.client, r.id.clock];
|
|
11
|
+
export const cmpStamp = (a, b) => a[0] - b[0] || a[1] - b[1] || a[2] - b[2];
|
|
12
|
+
/** Whether `a` and `b` are one unit. */
|
|
13
|
+
export const sameUnit = (a, b) => a.c === b.c && a.k === b.k;
|
|
14
|
+
const ops = engineOps(Y);
|
|
15
|
+
/** The item holding unit `u`, or `null` when `doc` lacks it (no value read: the fold's hot path). */
|
|
16
|
+
export const itemAt = (doc, u) => {
|
|
17
|
+
const structs = clientsOf(doc).get(u.c);
|
|
18
|
+
if (structs === undefined || u.k >= storeOf(doc).getClock(u.c))
|
|
19
|
+
return null;
|
|
20
|
+
const item = structAt(Y, structs, u.k);
|
|
21
|
+
return item === null || !ops.isItem(item) ? null : item;
|
|
22
|
+
};
|
|
23
|
+
/** The item holding unit `u` and the value at it, or `null` when `doc` lacks it (a GC struct, not arrived). */
|
|
24
|
+
export const unitOf = (doc, u) => {
|
|
25
|
+
const item = itemAt(doc, u);
|
|
26
|
+
return item === null ? null : { item, value: item.content.arr?.[u.k - item.id.clock] };
|
|
27
|
+
};
|
|
28
|
+
/**
|
|
29
|
+
* The units item `it` can show, in order, those `keep` takes: a live
|
|
30
|
+
* record's named units (present, not gone; `via` the record), a slot item's
|
|
31
|
+
* units, moved away or not (none when it is gone; `via` `null`). THE walk
|
|
32
|
+
* every reader of a sequence's units takes: the engine's (`shownBy`), the
|
|
33
|
+
* index's places (`placement/resolve.ts`) and the writers' view of them
|
|
34
|
+
* (`placement/model.ts`).
|
|
35
|
+
*/
|
|
36
|
+
export const unitsOf = (doc, it, keep) => {
|
|
37
|
+
const out = [];
|
|
38
|
+
const add = (item, value, unit, via) => {
|
|
39
|
+
if (item.gone !== true && keep(item, value, unit))
|
|
40
|
+
out.push({ value, unit, via, at: out.length });
|
|
41
|
+
};
|
|
42
|
+
if (!isRecord(it))
|
|
43
|
+
it.content.arr?.forEach((value, j) => add(it, value, { c: it.id.client, k: it.id.clock + j }, null));
|
|
44
|
+
else if (!it.deleted) {
|
|
45
|
+
const ids = it.content.ids;
|
|
46
|
+
for (let i = 0; i < ids.length; i += 3)
|
|
47
|
+
for (let k = 0; k < ids[i + 2]; k++) {
|
|
48
|
+
const unit = { c: ids[i], k: ids[i + 1] + k };
|
|
49
|
+
const u = unitOf(doc, unit);
|
|
50
|
+
if (u !== null)
|
|
51
|
+
add(u.item, u.value, unit, it);
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
return out;
|
|
55
|
+
};
|
|
56
|
+
/** The units the engine shows at item `it`, in order: a live record's, those it wins; a live slot item's, all. */
|
|
57
|
+
export const shownBy = (doc, it) => {
|
|
58
|
+
const via = isRecord(it) ? it : null;
|
|
59
|
+
return it.deleted ? [] : unitsOf(doc, it, (item) => item.mover === via);
|
|
60
|
+
};
|
|
61
|
+
/** The displayed entries of `node`'s sequence, in order. */
|
|
62
|
+
export const entriesOf = (doc, node) => {
|
|
63
|
+
const out = [];
|
|
64
|
+
for (let it = firstItem(node); it !== null; it = it.right)
|
|
65
|
+
for (const e of shownBy(doc, it))
|
|
66
|
+
out.push(e);
|
|
67
|
+
return out;
|
|
68
|
+
};
|
|
69
|
+
/**
|
|
70
|
+
* The entry unit `home` displays as: at home when no live record wins it,
|
|
71
|
+
* else at its mover (`via`, `at`); `null` when it shows nowhere (gone).
|
|
72
|
+
*/
|
|
73
|
+
export const anchorOf = (doc, home) => {
|
|
74
|
+
const u = unitOf(doc, home);
|
|
75
|
+
if (u === null || u.item.gone === true)
|
|
76
|
+
return null;
|
|
77
|
+
const r = u.item.mover ?? null;
|
|
78
|
+
if (r === null || r.deleted)
|
|
79
|
+
return { value: u.value, unit: home, via: null, at: 0 };
|
|
80
|
+
return shownBy(doc, r).find((e) => sameUnit(e.unit, home)) ?? null;
|
|
81
|
+
};
|
|
82
|
+
/** The sequence node that holds `e`'s displaying item (its record's, or its slot's). */
|
|
83
|
+
export const nodeOf = (doc, e) => (e.via !== null ? e.via.parent : unitOf(doc, e.unit).item.parent);
|
|
84
|
+
/** The live index of the item that displays `e` (its record, or its slot unit). */
|
|
85
|
+
const liveIndexOf = (doc, e) => {
|
|
86
|
+
const id = e.via === null ? Y.createID(e.unit.c, e.unit.k) : Y.createID(e.via.id.client, e.via.id.clock);
|
|
87
|
+
return Y.indexOfId(asYDoc(doc), id).index;
|
|
88
|
+
};
|
|
89
|
+
/** `units` as `client, clock, len` triples (one unit each). */
|
|
90
|
+
export const triples = (units) => units.flatMap((u) => [u.c, u.k, 1]);
|
|
91
|
+
/**
|
|
92
|
+
* Write new slots (`payloads`) and/or a move of `moved` (unit triples) into
|
|
93
|
+
* `node`'s sequence right after the displayed entry `left` (at the start when
|
|
94
|
+
* `null`; `right` the entry that follows, if any). When `left` is a unit in
|
|
95
|
+
* the middle of a record's display, the record written here carries that
|
|
96
|
+
* record's later units too, so the new entries show between them. `side` is
|
|
97
|
+
* the record's class among the items of its origin (`0` after, `1` before);
|
|
98
|
+
* `chain` the units the record stands at for the engine's sort of two
|
|
99
|
+
* records at one origin, given its first unit — the home slots from the root
|
|
100
|
+
* sequence down to the unit it stands at (`null`: the first unit alone) — and
|
|
101
|
+
* `part` its order among the records standing there. `shown` reads the units
|
|
102
|
+
* a record shows, as the reader sees them (the index's: a unit a cycle sent
|
|
103
|
+
* back elsewhere is not carried).
|
|
104
|
+
*/
|
|
105
|
+
export const placeAfter = (doc, node, left, right, payloads, moved = null, side = 0, part = 0, chain = null, shown = (r) => shownBy(doc, r)) => {
|
|
106
|
+
let index;
|
|
107
|
+
let carry = [];
|
|
108
|
+
if (left === null)
|
|
109
|
+
index = right === null ? node.length : liveIndexOf(doc, right);
|
|
110
|
+
else {
|
|
111
|
+
if (left.via !== null)
|
|
112
|
+
carry = shown(left.via)
|
|
113
|
+
.slice(left.at + 1)
|
|
114
|
+
.map((e) => e.unit);
|
|
115
|
+
// A unit shown at home though its item moved away (a cycle sent it back)
|
|
116
|
+
// is no live unit: the gap after it starts at its live index.
|
|
117
|
+
const live = left.via !== null || unitOf(doc, left.unit)?.item.deleted !== true;
|
|
118
|
+
index = liveIndexOf(doc, left) + (live ? 1 : 0);
|
|
119
|
+
}
|
|
120
|
+
const units = moved === null ? [] : [...moved];
|
|
121
|
+
if (payloads !== null && payloads.length > 0) {
|
|
122
|
+
// The new slots' first unit is this writer's next clock.
|
|
123
|
+
if (carry.length > 0)
|
|
124
|
+
units.push(doc.clientID, storeOf(doc).getClock(doc.clientID), payloads.length);
|
|
125
|
+
node.insert(index, payloads);
|
|
126
|
+
}
|
|
127
|
+
// A carried unit that moves here anyway is named once.
|
|
128
|
+
const named = new Set();
|
|
129
|
+
for (let i = 0; i < units.length; i += 3)
|
|
130
|
+
for (let k = 0; k < units[i + 2]; k++)
|
|
131
|
+
named.add(`${units[i]}:${units[i + 1] + k}`);
|
|
132
|
+
for (const u of carry)
|
|
133
|
+
if (!named.has(`${u.c}:${u.k}`))
|
|
134
|
+
units.push(u.c, u.k, 1);
|
|
135
|
+
if (units.length > 0 && (moved !== null || carry.length > 0)) {
|
|
136
|
+
const first = { c: units[0], k: units[1] };
|
|
137
|
+
const at = chain === null ? null : chain(first);
|
|
138
|
+
// A chain of the first unit alone is the engine's default: not written.
|
|
139
|
+
const own = at !== null && at.length === 1 && sameUnit(at[0], first);
|
|
140
|
+
node.move(index, units, side, part, at === null || own ? null : at.map((u) => Y.createID(u.c, u.k)));
|
|
141
|
+
}
|
|
142
|
+
};
|
|
143
|
+
/** Remove unit `u` from its sequence (the engine's delete: it shows nowhere any more). */
|
|
144
|
+
export const removeUnit = (doc, u) => {
|
|
145
|
+
Y.transact(asYDoc(doc), (tr) => {
|
|
146
|
+
const it = Y.getItemCleanStart(tr, Y.createID(u.c, u.k));
|
|
147
|
+
if (it.length > 1)
|
|
148
|
+
Y.getItemCleanStart(tr, Y.createID(u.c, u.k + 1));
|
|
149
|
+
if (!it.removed)
|
|
150
|
+
ops.remove(tr, it);
|
|
151
|
+
});
|
|
152
|
+
};
|
package/dist/crdt/protocol.d.ts
CHANGED
|
@@ -29,7 +29,7 @@ export { SCHEMA_VERSION, META_KEY } from './edytor-doc.js';
|
|
|
29
29
|
export { GENERATION, generationWord, frame, PROTOCOL_VERSION, GENERATION_RECORD, GenerationMismatchError, readProtocolVersion, type GenerationRecord } from './protocols/envelope.js';
|
|
30
30
|
export { messageSync, messageAwareness, messageAuth, messageQueryAwareness, messageSaved, messageChunk, messageComments, MAX_FRAME_BYTES, chunkFrame, createChunkReader, ChunkError, type ChunkReader, CLOSE } from './providers/room.js';
|
|
31
31
|
export { messageYjsSyncStep1, messageYjsSyncStep2, messageYjsUpdate, type SyncProtocol } from './protocols/sync.js';
|
|
32
|
-
export { messagePermissionDenied, messageReadOnly, writePermissionDenied, writeReadOnly } from './protocols/auth.js';
|
|
32
|
+
export { messageAccess, messagePermissionDenied, messageReadOnly, writeAccess, writePermissionDenied, writeReadOnly } from './protocols/auth.js';
|
|
33
33
|
export { commentsSubscribe, commentsSnapshot, commentsChange, writeCommentsSubscribe, writeCommentsSnapshot, writeCommentsChange, readCommentsMessage, type CommentChange, type CommentMessage, type CommentSnapshot, type CommentThread, type ThreadComment } from './protocols/comments.js';
|
|
34
34
|
export { applyAwarenessUpdate, encodeAwarenessUpdate, readAwarenessEntries, writeAwarenessEntries, type AwarenessEntry } from './protocols/awareness.js';
|
|
35
35
|
export { createDecoder, readVarUint, readVarUint8Array, type Decoder } from 'lib0-v14/decoding';
|
package/dist/crdt/protocol.js
CHANGED
|
@@ -32,7 +32,7 @@ export { GENERATION, generationWord, frame, PROTOCOL_VERSION, GENERATION_RECORD,
|
|
|
32
32
|
// ── Message types, acknowledgements and chunks ─────────────────────────
|
|
33
33
|
export { messageSync, messageAwareness, messageAuth, messageQueryAwareness, messageSaved, messageChunk, messageComments, MAX_FRAME_BYTES, chunkFrame, createChunkReader, ChunkError, CLOSE } from './providers/room.js';
|
|
34
34
|
export { messageYjsSyncStep1, messageYjsSyncStep2, messageYjsUpdate } from './protocols/sync.js';
|
|
35
|
-
export { messagePermissionDenied, messageReadOnly, writePermissionDenied, writeReadOnly } from './protocols/auth.js';
|
|
35
|
+
export { messageAccess, messagePermissionDenied, messageReadOnly, writeAccess, writePermissionDenied, writeReadOnly } from './protocols/auth.js';
|
|
36
36
|
export { commentsSubscribe, commentsSnapshot, commentsChange, writeCommentsSubscribe, writeCommentsSnapshot, writeCommentsChange, readCommentsMessage } from './protocols/comments.js';
|
|
37
37
|
export { applyAwarenessUpdate, encodeAwarenessUpdate, readAwarenessEntries, writeAwarenessEntries } from './protocols/awareness.js';
|
|
38
38
|
// Minimal wire codec for frame bodies (lib0 — the codec the frames are
|
|
@@ -14,10 +14,20 @@ import * as decoding from 'lib0-v14/decoding';
|
|
|
14
14
|
export declare const messagePermissionDenied = 0;
|
|
15
15
|
/** The denial of a read-only socket's write: that socket still syncs (not a terminal refusal). */
|
|
16
16
|
export declare const READ_ONLY_DENIAL = "read-only";
|
|
17
|
+
/** The denial of what a suggest-only socket wrote that is no suggestion's: it still syncs. */
|
|
18
|
+
export declare const SUGGEST_ONLY_DENIAL = "suggest-only";
|
|
17
19
|
export declare const writePermissionDenied: (encoder: encoding.Encoder, reason: string) => void;
|
|
18
20
|
/** The socket may read but not write: sent when it joins, before any write is refused. */
|
|
19
21
|
export declare const messageReadOnly = 1;
|
|
20
22
|
export declare const writeReadOnly: (encoder: encoding.Encoder) => void;
|
|
23
|
+
/**
|
|
24
|
+
* The socket's access (the string that follows: `write`, `suggest` or
|
|
25
|
+
* `comment`), sent when it joins, before anything else it reads; a
|
|
26
|
+
* `comment` one's after its read-only notice (a `read` one has only the
|
|
27
|
+
* notice).
|
|
28
|
+
*/
|
|
29
|
+
export declare const messageAccess = 2;
|
|
30
|
+
export declare const writeAccess: (encoder: encoding.Encoder, access: string) => void;
|
|
21
31
|
export type PermissionDeniedHandler = (ydoc: unknown, reason: string) => void;
|
|
22
32
|
/**
|
|
23
33
|
* Decode one auth payload. Returns the auth message subtype so the caller
|
|
@@ -25,4 +35,4 @@ export type PermissionDeniedHandler = (ydoc: unknown, reason: string) => void;
|
|
|
25
35
|
* do not speak is protocol skew, and this stack's contract is that skew is
|
|
26
36
|
* reported, not silently dropped (same rule as unknown sync subtypes).
|
|
27
37
|
*/
|
|
28
|
-
export declare const readAuthMessage: (decoder: decoding.Decoder, y: unknown, permissionDeniedHandler: PermissionDeniedHandler, readOnlyHandler?: (ydoc: unknown) => void) => number;
|
|
38
|
+
export declare const readAuthMessage: (decoder: decoding.Decoder, y: unknown, permissionDeniedHandler: PermissionDeniedHandler, readOnlyHandler?: (ydoc: unknown) => void, accessHandler?: (access: string) => void) => number;
|
|
@@ -14,6 +14,8 @@ import * as decoding from 'lib0-v14/decoding';
|
|
|
14
14
|
export const messagePermissionDenied = 0;
|
|
15
15
|
/** The denial of a read-only socket's write: that socket still syncs (not a terminal refusal). */
|
|
16
16
|
export const READ_ONLY_DENIAL = 'read-only';
|
|
17
|
+
/** The denial of what a suggest-only socket wrote that is no suggestion's: it still syncs. */
|
|
18
|
+
export const SUGGEST_ONLY_DENIAL = 'suggest-only';
|
|
17
19
|
export const writePermissionDenied = (encoder, reason) => {
|
|
18
20
|
encoding.writeVarUint(encoder, messagePermissionDenied);
|
|
19
21
|
encoding.writeVarString(encoder, reason);
|
|
@@ -23,13 +25,24 @@ export const messageReadOnly = 1;
|
|
|
23
25
|
export const writeReadOnly = (encoder) => {
|
|
24
26
|
encoding.writeVarUint(encoder, messageReadOnly);
|
|
25
27
|
};
|
|
28
|
+
/**
|
|
29
|
+
* The socket's access (the string that follows: `write`, `suggest` or
|
|
30
|
+
* `comment`), sent when it joins, before anything else it reads; a
|
|
31
|
+
* `comment` one's after its read-only notice (a `read` one has only the
|
|
32
|
+
* notice).
|
|
33
|
+
*/
|
|
34
|
+
export const messageAccess = 2;
|
|
35
|
+
export const writeAccess = (encoder, access) => {
|
|
36
|
+
encoding.writeVarUint(encoder, messageAccess);
|
|
37
|
+
encoding.writeVarString(encoder, access);
|
|
38
|
+
};
|
|
26
39
|
/**
|
|
27
40
|
* Decode one auth payload. Returns the auth message subtype so the caller
|
|
28
41
|
* can surface unknown subtypes — a valid envelope carrying an auth type we
|
|
29
42
|
* do not speak is protocol skew, and this stack's contract is that skew is
|
|
30
43
|
* reported, not silently dropped (same rule as unknown sync subtypes).
|
|
31
44
|
*/
|
|
32
|
-
export const readAuthMessage = (decoder, y, permissionDeniedHandler, readOnlyHandler) => {
|
|
45
|
+
export const readAuthMessage = (decoder, y, permissionDeniedHandler, readOnlyHandler, accessHandler) => {
|
|
33
46
|
const authMessageType = decoding.readVarUint(decoder);
|
|
34
47
|
switch (authMessageType) {
|
|
35
48
|
case messagePermissionDenied:
|
|
@@ -38,6 +51,9 @@ export const readAuthMessage = (decoder, y, permissionDeniedHandler, readOnlyHan
|
|
|
38
51
|
case messageReadOnly:
|
|
39
52
|
// Unclaimed, it is returned like any subtype the caller does not speak.
|
|
40
53
|
readOnlyHandler?.(y);
|
|
54
|
+
break;
|
|
55
|
+
case messageAccess:
|
|
56
|
+
accessHandler?.(decoding.readVarString(decoder));
|
|
41
57
|
}
|
|
42
58
|
return authMessageType;
|
|
43
59
|
};
|
|
@@ -67,6 +67,16 @@ export type CommentRequest = {
|
|
|
67
67
|
block?: string | null;
|
|
68
68
|
/** The first comment's id (default: one the store makes). */
|
|
69
69
|
comment?: string;
|
|
70
|
+
/**
|
|
71
|
+
* The text it is made on, as anchors (`DocAnchor`s, each span's
|
|
72
|
+
* ends): the room writes its anchor mark over them for a user who
|
|
73
|
+
* may only comment (`room.access.comment`); a writer's view writes
|
|
74
|
+
* it itself.
|
|
75
|
+
*/
|
|
76
|
+
anchors?: {
|
|
77
|
+
start: unknown;
|
|
78
|
+
end: unknown;
|
|
79
|
+
}[];
|
|
70
80
|
} | {
|
|
71
81
|
op: 'reply';
|
|
72
82
|
thread: string;
|
|
@@ -85,11 +95,16 @@ export type CommentRequest = {
|
|
|
85
95
|
thread: string;
|
|
86
96
|
comment?: string;
|
|
87
97
|
};
|
|
88
|
-
/**
|
|
98
|
+
/**
|
|
99
|
+
* Who asks: a verified user, read-only or not; a moderator may delete
|
|
100
|
+
* anyone's comment; `anchors`: the store writes a new thread's anchor (a
|
|
101
|
+
* user who may only comment).
|
|
102
|
+
*/
|
|
89
103
|
export type CommentActor = {
|
|
90
104
|
user: string;
|
|
91
105
|
readOnly?: boolean;
|
|
92
106
|
moderator?: boolean;
|
|
107
|
+
anchors?: boolean;
|
|
93
108
|
};
|
|
94
109
|
/** What one request changed (the room broadcasts it, `onComment` receives it). */
|
|
95
110
|
export type CommentChange = {
|
|
@@ -62,13 +62,21 @@ export const parseCommentRequest = (value) => {
|
|
|
62
62
|
return null;
|
|
63
63
|
if (v.block !== undefined && v.block !== null && typeof v.block !== 'string')
|
|
64
64
|
return null;
|
|
65
|
+
if (v.anchors !== undefined &&
|
|
66
|
+
(!Array.isArray(v.anchors) ||
|
|
67
|
+
v.anchors.length > 64 ||
|
|
68
|
+
v.anchors.some((a) => typeof a?.start !== 'object' || typeof a?.end !== 'object')))
|
|
69
|
+
return null;
|
|
65
70
|
return {
|
|
66
71
|
op: 'add',
|
|
67
72
|
thread: v.thread,
|
|
68
73
|
body: v.body,
|
|
69
74
|
...(v.quote === undefined ? {} : { quote: v.quote }),
|
|
70
75
|
...(v.block === undefined ? {} : { block: v.block }),
|
|
71
|
-
...(v.comment === undefined ? {} : { comment: v.comment })
|
|
76
|
+
...(v.comment === undefined ? {} : { comment: v.comment }),
|
|
77
|
+
...(v.anchors === undefined
|
|
78
|
+
? {}
|
|
79
|
+
: { anchors: v.anchors })
|
|
72
80
|
};
|
|
73
81
|
case 'reply':
|
|
74
82
|
if (typeof v.body !== 'string')
|
|
@@ -47,6 +47,8 @@ export type EdytorSyncPayload = {
|
|
|
47
47
|
* websocket sync's local store. `document.attachSync` passes it.
|
|
48
48
|
*/
|
|
49
49
|
attach?: (sync: EdytorSync) => EdytorSyncCleanup | void;
|
|
50
|
+
/** What the room says the document's user may do (`EdytorDocument.access`). */
|
|
51
|
+
access?: (access: 'write' | 'suggest' | 'comment' | 'read') => void;
|
|
50
52
|
};
|
|
51
53
|
export type EdytorSyncCleanup = () => void | Promise<void>;
|
|
52
54
|
export type EdytorSync = ((payload: EdytorSyncPayload) => void | EdytorSyncCleanup) & {
|
|
@@ -273,11 +275,11 @@ export declare const bindProviders: (Y: EngineApi) => {
|
|
|
273
275
|
destroy(): void;
|
|
274
276
|
disconnect(): void;
|
|
275
277
|
connect(): void;
|
|
276
|
-
emit<N extends "refused" | "status" | "failed" | "schema-mismatch" | "message-error" | "protocol-mismatch" | "synced" | "connection-close" | "connection-error" | "saved" | "permission-denied" | "expired" | "unreachable">(name: N, args: Parameters<WebsocketProviderEvents[N]>): void;
|
|
278
|
+
emit<N extends "refused" | "status" | "failed" | "schema-mismatch" | "message-error" | "protocol-mismatch" | "synced" | "access" | "connection-close" | "connection-error" | "saved" | "permission-denied" | "expired" | "unreachable">(name: N, args: Parameters<WebsocketProviderEvents[N]>): void;
|
|
277
279
|
_observers: Map<string, Set<any>>;
|
|
278
|
-
on<NAME extends "refused" | "status" | "failed" | "schema-mismatch" | "message-error" | "protocol-mismatch" | "synced" | "connection-close" | "connection-error" | "saved" | "permission-denied" | "expired" | "unreachable">(name: NAME, f: WebsocketProviderEvents[NAME]): WebsocketProviderEvents[NAME];
|
|
279
|
-
once<NAME extends "refused" | "status" | "failed" | "schema-mismatch" | "message-error" | "protocol-mismatch" | "synced" | "connection-close" | "connection-error" | "saved" | "permission-denied" | "expired" | "unreachable">(name: NAME, f: WebsocketProviderEvents[NAME]): void;
|
|
280
|
-
off<NAME extends "refused" | "status" | "failed" | "schema-mismatch" | "message-error" | "protocol-mismatch" | "synced" | "connection-close" | "connection-error" | "saved" | "permission-denied" | "expired" | "unreachable">(name: NAME, f: WebsocketProviderEvents[NAME]): void;
|
|
280
|
+
on<NAME extends "refused" | "status" | "failed" | "schema-mismatch" | "message-error" | "protocol-mismatch" | "synced" | "access" | "connection-close" | "connection-error" | "saved" | "permission-denied" | "expired" | "unreachable">(name: NAME, f: WebsocketProviderEvents[NAME]): WebsocketProviderEvents[NAME];
|
|
281
|
+
once<NAME extends "refused" | "status" | "failed" | "schema-mismatch" | "message-error" | "protocol-mismatch" | "synced" | "access" | "connection-close" | "connection-error" | "saved" | "permission-denied" | "expired" | "unreachable">(name: NAME, f: WebsocketProviderEvents[NAME]): void;
|
|
282
|
+
off<NAME extends "refused" | "status" | "failed" | "schema-mismatch" | "message-error" | "protocol-mismatch" | "synced" | "access" | "connection-close" | "connection-error" | "saved" | "permission-denied" | "expired" | "unreachable">(name: NAME, f: WebsocketProviderEvents[NAME]): void;
|
|
281
283
|
};
|
|
282
284
|
};
|
|
283
285
|
watchComments: (server: string | null, roomName: string, listener: (message: import("../protocol.js").CommentMessage) => void) => (() => void);
|
|
@@ -38,7 +38,7 @@ export const bindProviders = (Y) => {
|
|
|
38
38
|
const room = `${serverUrl}/${roomName}`;
|
|
39
39
|
const persistName = options.persist === false ? undefined : (options.persistName ?? `edytor:${room}`);
|
|
40
40
|
const sync = (payload) => {
|
|
41
|
-
const { doc, awareness, synced, failed, attach, armBound, holdBound } = payload;
|
|
41
|
+
const { doc, awareness, synced, failed, attach, armBound, holdBound, access } = payload;
|
|
42
42
|
const { params, WebSocketPolyfill, maxBackoffTime, connectTimeout, disableBc, onExpired, maxFrameBytes } = options;
|
|
43
43
|
const local = persistName !== undefined && typeof indexedDB !== 'undefined'
|
|
44
44
|
? localSync(persistName, { disableBc })
|
|
@@ -54,6 +54,8 @@ export const bindProviders = (Y) => {
|
|
|
54
54
|
});
|
|
55
55
|
if (onExpired)
|
|
56
56
|
provider.on('expired', (state) => onExpired(state));
|
|
57
|
+
if (access)
|
|
58
|
+
provider.on('access', access);
|
|
57
59
|
provider.on('synced', (isSynced) => {
|
|
58
60
|
if (isSynced)
|
|
59
61
|
synced(provider);
|
|
@@ -139,13 +139,27 @@ export type LifecycleHost = {
|
|
|
139
139
|
_leave: () => void;
|
|
140
140
|
emit?(event: 'failed', args: [unknown, unknown]): void;
|
|
141
141
|
};
|
|
142
|
+
/** When a subscriber of {@link onPageLeave} runs: a session's last writes, then the transports. */
|
|
143
|
+
export type PageLeavePhase = 'write' | 'transport';
|
|
144
|
+
/**
|
|
145
|
+
* Be told when the page leaves for good: a `pagehide` of a page the
|
|
146
|
+
* back/forward cache keeps nothing of (a kept page may come back as it was),
|
|
147
|
+
* or, in Node, the process's `exit`. One listener for the page; its
|
|
148
|
+
* subscribers run in two phases whatever order they subscribed in — a
|
|
149
|
+
* session's last writes (`write`: a history's release, `hist.release.end`)
|
|
150
|
+
* before the transports' teardown (`transport`: each provider), so those
|
|
151
|
+
* writes go out while the sockets and stores are still open (listener order
|
|
152
|
+
* on the window is registration order in Chromium, capturing or not).
|
|
153
|
+
* Returns the unsubscribe.
|
|
154
|
+
*/
|
|
155
|
+
export declare const onPageLeave: (fn: () => void, phase: PageLeavePhase) => (() => void);
|
|
142
156
|
/**
|
|
143
157
|
* Install the lifecycle on a provider: `hasSynced`, `whenSynced`, and the
|
|
144
158
|
* departure announcement. `beforeunload` only announces it (`depart`): an
|
|
145
159
|
* app's unsaved-changes prompt fires it and the user may stay, so the
|
|
146
160
|
* provider keeps running and its presence returns at the next renewal.
|
|
147
|
-
* Leaving the page
|
|
148
|
-
*
|
|
161
|
+
* Leaving the page for good ({@link onPageLeave}'s `transport` phase)
|
|
162
|
+
* destroys the provider, whose teardown announces it.
|
|
149
163
|
*/
|
|
150
164
|
export declare const initLifecycle: (host: LifecycleHost, destroy: () => unknown, depart?: () => void) => void;
|
|
151
165
|
/** The first sync — lifetime. Returns `true` only the first time. */
|
|
@@ -45,6 +45,7 @@ import * as encoding from 'lib0-v14/encoding';
|
|
|
45
45
|
import * as decoding from 'lib0-v14/decoding';
|
|
46
46
|
import * as promise from 'lib0-v14/promise';
|
|
47
47
|
import { applyAwarenessUpdate, encodeAwarenessUpdate } from '../protocols/awareness.js';
|
|
48
|
+
import { callEach } from '../protocols/observable.js';
|
|
48
49
|
import { BARE_REPLY_LENGTH, frame, GENERATION, writeProtocolVersion } from '../protocols/envelope.js';
|
|
49
50
|
import { checkSchema, SchemaMismatchError } from '../admission.js';
|
|
50
51
|
import { asEngineDoc } from '../structs.js';
|
|
@@ -205,13 +206,68 @@ export const tabs = {
|
|
|
205
206
|
};
|
|
206
207
|
/** The outbound quarantine: a read-only document does not spread. */
|
|
207
208
|
export const quarantined = (doc) => checkSchema(asEngineDoc(doc)) !== null;
|
|
209
|
+
/**
|
|
210
|
+
* The departures installed, by the page's `addEventListener` (one per realm
|
|
211
|
+
* in a browser; a harness simulating several pages in one realm gives each
|
|
212
|
+
* its own) or, in Node, by the process.
|
|
213
|
+
*/
|
|
214
|
+
const departures = new WeakMap();
|
|
215
|
+
/** The page (Node: the process) leaves for good: the writes first, then the transports, each isolated. */
|
|
216
|
+
const leave = (departure) => {
|
|
217
|
+
for (const phase of ['write', 'transport'])
|
|
218
|
+
callEach(`[edytor] page leave (${phase})`, [...departure.leaving[phase]]);
|
|
219
|
+
};
|
|
220
|
+
/**
|
|
221
|
+
* Be told when the page leaves for good: a `pagehide` of a page the
|
|
222
|
+
* back/forward cache keeps nothing of (a kept page may come back as it was),
|
|
223
|
+
* or, in Node, the process's `exit`. One listener for the page; its
|
|
224
|
+
* subscribers run in two phases whatever order they subscribed in — a
|
|
225
|
+
* session's last writes (`write`: a history's release, `hist.release.end`)
|
|
226
|
+
* before the transports' teardown (`transport`: each provider), so those
|
|
227
|
+
* writes go out while the sockets and stores are still open (listener order
|
|
228
|
+
* on the window is registration order in Chromium, capturing or not).
|
|
229
|
+
* Returns the unsubscribe.
|
|
230
|
+
*/
|
|
231
|
+
export const onPageLeave = (fn, phase) => {
|
|
232
|
+
const page = globalThis;
|
|
233
|
+
const proc = globalThis.process ?? {};
|
|
234
|
+
const key = page.addEventListener ?? proc;
|
|
235
|
+
let departure = departures.get(key);
|
|
236
|
+
if (departure === undefined) {
|
|
237
|
+
const created = {
|
|
238
|
+
leaving: { write: new Set(), transport: new Set() },
|
|
239
|
+
uninstall: () => { }
|
|
240
|
+
};
|
|
241
|
+
if (page.addEventListener) {
|
|
242
|
+
const hide = (event) => void (event.persisted || leave(created));
|
|
243
|
+
const remove = page.removeEventListener;
|
|
244
|
+
page.addEventListener('pagehide', hide);
|
|
245
|
+
created.uninstall = () => remove?.call(page, 'pagehide', hide);
|
|
246
|
+
}
|
|
247
|
+
else {
|
|
248
|
+
const exit = () => leave(created);
|
|
249
|
+
proc.on?.('exit', exit);
|
|
250
|
+
created.uninstall = () => proc.off?.('exit', exit);
|
|
251
|
+
}
|
|
252
|
+
departures.set(key, (departure = created));
|
|
253
|
+
}
|
|
254
|
+
const own = departure;
|
|
255
|
+
own.leaving[phase].add(fn);
|
|
256
|
+
return () => {
|
|
257
|
+
own.leaving[phase].delete(fn);
|
|
258
|
+
if (own.leaving.write.size + own.leaving.transport.size === 0 && departures.get(key) === own) {
|
|
259
|
+
own.uninstall();
|
|
260
|
+
departures.delete(key);
|
|
261
|
+
}
|
|
262
|
+
};
|
|
263
|
+
};
|
|
208
264
|
/**
|
|
209
265
|
* Install the lifecycle on a provider: `hasSynced`, `whenSynced`, and the
|
|
210
266
|
* departure announcement. `beforeunload` only announces it (`depart`): an
|
|
211
267
|
* app's unsaved-changes prompt fires it and the user may stay, so the
|
|
212
268
|
* provider keeps running and its presence returns at the next renewal.
|
|
213
|
-
* Leaving the page
|
|
214
|
-
*
|
|
269
|
+
* Leaving the page for good ({@link onPageLeave}'s `transport` phase)
|
|
270
|
+
* destroys the provider, whose teardown announces it.
|
|
215
271
|
*/
|
|
216
272
|
export const initLifecycle = (host, destroy, depart = () => { }) => {
|
|
217
273
|
host.hasSynced = false;
|
|
@@ -222,21 +278,12 @@ export const initLifecycle = (host, destroy, depart = () => { }) => {
|
|
|
222
278
|
// A consumer that never attaches a catch must not crash the process.
|
|
223
279
|
host.whenSynced.catch(() => { });
|
|
224
280
|
const page = globalThis;
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
page.
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
page.removeEventListener?.('pagehide', leave);
|
|
232
|
-
};
|
|
233
|
-
}
|
|
234
|
-
else {
|
|
235
|
-
const leave = () => void destroy();
|
|
236
|
-
const proc = globalThis.process ?? {};
|
|
237
|
-
proc.on?.('exit', leave);
|
|
238
|
-
host._leave = () => proc.off?.('exit', leave);
|
|
239
|
-
}
|
|
281
|
+
page.addEventListener?.('beforeunload', depart);
|
|
282
|
+
const off = onPageLeave(() => void destroy(), 'transport');
|
|
283
|
+
host._leave = () => {
|
|
284
|
+
page.removeEventListener?.('beforeunload', depart);
|
|
285
|
+
off();
|
|
286
|
+
};
|
|
240
287
|
};
|
|
241
288
|
/** The first sync — lifetime. Returns `true` only the first time. */
|
|
242
289
|
export const markSynced = (host) => {
|