edytor 6.0.0 → 7.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 +3 -3
- package/dist/cloudflare/DocumentRoom.d.ts +77 -2
- package/dist/cloudflare/DocumentRoom.js +37 -1
- package/dist/cloudflare/index.d.ts +1 -1
- package/dist/cloudflare/index.js +1 -1
- package/dist/cloudflare/room/access.d.ts +6 -1
- package/dist/cloudflare/room/access.js +30 -4
- package/dist/cloudflare/room/admission.d.ts +11 -0
- package/dist/cloudflare/room/admission.js +43 -10
- package/dist/cloudflare/room/context.d.ts +11 -2
- package/dist/cloudflare/room/context.js +17 -6
- package/dist/cloudflare/room/forged.d.ts +1 -1
- package/dist/cloudflare/room/forged.js +11 -9
- package/dist/cloudflare/room/frames.d.ts +2 -0
- package/dist/cloudflare/room/frames.js +2 -0
- package/dist/cloudflare/room/presence.js +1 -4
- package/dist/cloudflare/room/reseed.d.ts +93 -0
- package/dist/cloudflare/room/reseed.js +500 -0
- package/dist/cloudflare/room/scheduler.d.ts +1 -1
- package/dist/cloudflare/room/scheduler.js +9 -1
- package/dist/cloudflare/room/shared.d.ts +10 -0
- package/dist/cloudflare/room/shared.js +10 -0
- package/dist/cloudflare/room/storage.d.ts +7 -0
- package/dist/cloudflare/room/storage.js +33 -1
- package/dist/components/Edytor.svelte +18 -3
- package/dist/components/Edytor.svelte.d.ts +10 -1
- package/dist/components/EdytorRoot.svelte +17 -0
- package/dist/components/EdytorRoot.svelte.d.ts +4 -0
- package/dist/components/Suggestion.svelte +1 -1
- package/dist/crdt/attribution/block.d.ts +3 -0
- package/dist/crdt/attribution/block.js +3 -2
- package/dist/crdt/doc/suggest.d.ts +6 -2
- package/dist/crdt/doc/suggest.js +6 -5
- package/dist/crdt/edytor-doc.d.ts +1 -1
- package/dist/crdt/edytor-doc.js +1 -1
- package/dist/crdt/engine-room.d.ts +4 -0
- package/dist/crdt/engine-room.js +6 -0
- package/dist/crdt/index.d.ts +2 -1
- package/dist/crdt/protocol.d.ts +2 -2
- package/dist/crdt/protocol.js +2 -2
- package/dist/crdt/protocols/auth.d.ts +8 -1
- package/dist/crdt/protocols/auth.js +14 -1
- package/dist/crdt/providers/index.d.ts +1 -0
- package/dist/crdt/providers/index.js +22 -3
- package/dist/crdt/providers/room.d.ts +15 -0
- package/dist/crdt/providers/room.js +15 -0
- package/dist/crdt/providers/websocket.d.ts +4 -2
- package/dist/crdt/providers/websocket.js +36 -9
- package/dist/crdt/reseed.d.ts +127 -0
- package/dist/crdt/reseed.js +533 -0
- package/dist/crdt/restore.d.ts +13 -1
- package/dist/crdt/restore.js +8 -3
- package/dist/crdt/schema.d.ts +6 -0
- package/dist/crdt/schema.js +6 -0
- package/dist/crdt/structs.d.ts +8 -11
- package/dist/crdt/structs.js +25 -44
- package/dist/crdt/text/index/build.d.ts +3 -9
- package/dist/crdt/text/index/build.js +87 -181
- package/dist/crdt/vendor/yjs/UPSTREAM.md +101 -5
- package/dist/crdt/vendor/yjs/dts/index.d.ts +1 -1
- package/dist/crdt/vendor/yjs/dts/utils/Transaction.d.ts +10 -10
- package/dist/crdt/vendor/yjs/dts/utils/encoding.d.ts +1 -0
- package/dist/crdt/vendor/yjs/src/index.js +1 -1
- package/dist/crdt/vendor/yjs/src/structs/Item.js +24 -13
- package/dist/crdt/vendor/yjs/src/utils/Transaction.js +12 -10
- package/dist/crdt/vendor/yjs/src/utils/encoding.js +42 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.js +1 -1
- package/dist/selection/selection.svelte.d.ts +1 -1
- package/dist/selection/selection.svelte.js +87 -106
- package/dist/session/commands.js +49 -51
- package/dist/surface/attributes.d.ts +3 -3
- package/dist/surface/attributes.js +2 -2
- package/dist/surface/observer.svelte.d.ts +7 -7
- package/dist/surface/observer.svelte.js +213 -309
- package/package.json +1 -1
|
@@ -9,7 +9,7 @@ export declare const isId: (value: unknown) => value is Id;
|
|
|
9
9
|
* The clients of a frame (but `skip`) that write a per-writer block mark
|
|
10
10
|
* of another writer: a struct whose key is `del.<n>` or `wd.<n>` on a
|
|
11
11
|
* block node, from a client other than `n`. Only `n` writes its mark.
|
|
12
|
-
* A struct in the purge horizon's root forges the room's.
|
|
12
|
+
* A struct in the purge horizon's root, or the epoch's, forges the room's.
|
|
13
13
|
*/
|
|
14
14
|
export declare const forgedWriters: (doc: YDoc, structs: Struct[], skip: Set<number>) => Set<number>;
|
|
15
15
|
/**
|
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Forged writes: what a frame writes or deletes that its sender may not —
|
|
3
3
|
* another writer's per-writer block mark (`room.marks.writer`), the purge
|
|
4
|
-
* horizon (the room's alone), another replica's binding or another user's
|
|
4
|
+
* horizon and the epoch (the room's alone), another replica's binding or another user's
|
|
5
5
|
* profile in the attribution root (`room.attribution.trust`). Pure reads of
|
|
6
6
|
* the room document and the frame; `Admission` strips or drops what they
|
|
7
7
|
* find.
|
|
8
8
|
*/
|
|
9
9
|
import { Y } from '../../crdt/engine.js';
|
|
10
|
-
import { ATTRIBUTION_ROOT, DEL_PREFIX, HORIZON_ROOT, REGISTRY_KEY, SUGGEST_ROOT, WITHDRAW_PREFIX } from '../../crdt/schema.js';
|
|
10
|
+
import { ATTRIBUTION_ROOT, DEL_PREFIX, EPOCH_ROOT, HORIZON_ROOT, REGISTRY_KEY, SUGGEST_ROOT, WITHDRAW_PREFIX } from '../../crdt/schema.js';
|
|
11
11
|
import { heldClock, runsOf, storedStruct, structAt } from './updates.js';
|
|
12
12
|
// ── Per-writer marks ──────────────────────────────────────────
|
|
13
13
|
/** The writer `n` of a per-writer block mark key (`del.<n>`, `wd.<n>`), or `null`. */
|
|
@@ -63,14 +63,16 @@ const isBlockNode = (doc, frame, parent) => {
|
|
|
63
63
|
const item = parent?._item;
|
|
64
64
|
return item?.parent === registry && registry !== undefined;
|
|
65
65
|
};
|
|
66
|
-
/**
|
|
67
|
-
|
|
68
|
-
|
|
66
|
+
/**
|
|
67
|
+
* Is `parent` (a {@link Place}'s) a root only the room writes: the purge
|
|
68
|
+
* horizon's, or the epoch's (`room.reseed.protocol`)?
|
|
69
|
+
*/
|
|
70
|
+
const isRoomRoot = (doc, parent) => [HORIZON_ROOT, EPOCH_ROOT].some((root) => parent === root || (parent != null && parent === doc.share.get(root)));
|
|
69
71
|
/**
|
|
70
72
|
* The clients of a frame (but `skip`) that write a per-writer block mark
|
|
71
73
|
* of another writer: a struct whose key is `del.<n>` or `wd.<n>` on a
|
|
72
74
|
* block node, from a client other than `n`. Only `n` writes its mark.
|
|
73
|
-
* A struct in the purge horizon's root forges the room's.
|
|
75
|
+
* A struct in the purge horizon's root, or the epoch's, forges the room's.
|
|
74
76
|
*/
|
|
75
77
|
export const forgedWriters = (doc, structs, skip) => {
|
|
76
78
|
const frame = runsOf(structs);
|
|
@@ -83,7 +85,7 @@ export const forgedWriters = (doc, structs, skip) => {
|
|
|
83
85
|
continue;
|
|
84
86
|
const place = placeOf(doc, frame, struct.id);
|
|
85
87
|
const writer = markWriter(place?.key);
|
|
86
|
-
if ((place !== null &&
|
|
88
|
+
if ((place !== null && isRoomRoot(doc, place.parent)) ||
|
|
87
89
|
(writer !== null && writer !== client && isBlockNode(doc, frame, place.parent))) {
|
|
88
90
|
forged.add(client);
|
|
89
91
|
}
|
|
@@ -113,8 +115,8 @@ export const forgedDeletes = (doc, ds, mayDelete) => {
|
|
|
113
115
|
break;
|
|
114
116
|
if (!(struct instanceof Y.Item) || struct.deleted)
|
|
115
117
|
continue;
|
|
116
|
-
// The purge horizon
|
|
117
|
-
if (!
|
|
118
|
+
// The purge horizon and the epoch are the room's alone.
|
|
119
|
+
if (!isRoomRoot(doc, struct.parent)) {
|
|
118
120
|
const writer = markWriter(struct.parentSub);
|
|
119
121
|
if (writer === null || mayDelete(writer))
|
|
120
122
|
continue;
|
|
@@ -29,6 +29,8 @@ export declare const readOnlyFrame: () => Uint8Array;
|
|
|
29
29
|
export declare const readOnlyDenialFrame: () => Uint8Array;
|
|
30
30
|
/** The denial of what a suggest-only socket may not write (stripped): it stays, and still syncs. */
|
|
31
31
|
export declare const suggestOnlyDenialFrame: () => Uint8Array;
|
|
32
|
+
/** The room's epoch, to a socket that dialed an older one (`room.reseed.protocol`). */
|
|
33
|
+
export declare const epochFrame: (epoch: number) => Uint8Array;
|
|
32
34
|
/** The socket's access, at the join and at a change in place (`write`, `suggest`, `comment`, `read`). */
|
|
33
35
|
export declare const accessFrame: (access: string) => Uint8Array;
|
|
34
36
|
/** A Step2 of what the room STORED: the engine's pending store (never stored) is left out. */
|
|
@@ -51,6 +51,8 @@ export const readOnlyFrame = () => E.frame(E.messageAuth, (e) => E.writeReadOnly
|
|
|
51
51
|
export const readOnlyDenialFrame = () => E.frame(E.messageAuth, (e) => E.writePermissionDenied(e, READ_ONLY_DENIAL));
|
|
52
52
|
/** The denial of what a suggest-only socket may not write (stripped): it stays, and still syncs. */
|
|
53
53
|
export const suggestOnlyDenialFrame = () => E.frame(E.messageAuth, (e) => E.writePermissionDenied(e, SUGGEST_ONLY_DENIAL));
|
|
54
|
+
/** The room's epoch, to a socket that dialed an older one (`room.reseed.protocol`). */
|
|
55
|
+
export const epochFrame = (epoch) => E.frame(E.messageAuth, (e) => E.writeEpoch(e, epoch));
|
|
54
56
|
/** The socket's access, at the join and at a change in place (`write`, `suggest`, `comment`, `read`). */
|
|
55
57
|
export const accessFrame = (access) => E.frame(E.messageAuth, (e) => E.writeAccess(e, access));
|
|
56
58
|
/** A Step2 of what the room STORED: the engine's pending store (never stored) is left out. */
|
|
@@ -7,7 +7,6 @@
|
|
|
7
7
|
* the socket goes.
|
|
8
8
|
*/
|
|
9
9
|
import { semanticsMismatch } from '../../crdt/semantics.js';
|
|
10
|
-
import { SOCKET_TAG } from './shared.js';
|
|
11
10
|
import { allowance } from './admission.js';
|
|
12
11
|
import { encodeJSON } from './context.js';
|
|
13
12
|
import { encodePresenceEntry, presenceFrame, presenceFrameOf } from './frames.js';
|
|
@@ -142,9 +141,7 @@ export class RoomPresence {
|
|
|
142
141
|
* replica, and {@link drain} sends each recipient's in one frame.
|
|
143
142
|
*/
|
|
144
143
|
fanOut(entries) {
|
|
145
|
-
const sockets = this.room.
|
|
146
|
-
.getWebSockets(SOCKET_TAG)
|
|
147
|
-
.filter((socket) => socket.readyState === WebSocket.OPEN);
|
|
144
|
+
const sockets = this.room.sockets();
|
|
148
145
|
if (sockets.length === 0)
|
|
149
146
|
return;
|
|
150
147
|
if (this.waiting.size === 0 && this.take(sockets.length)) {
|
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
import * as E from '../../crdt/protocol.js';
|
|
2
|
+
import type { YDoc } from '../../crdt/index.js';
|
|
3
|
+
import type { Attachment, SocketIdentity } from '../DocumentRoom.js';
|
|
4
|
+
import type { Decoded } from './context.js';
|
|
5
|
+
import { type RoomContext } from './context.js';
|
|
6
|
+
/** An epoch a dial names (`?epoch=`; `new`: a document that holds nothing yet), or `null`. */
|
|
7
|
+
export declare const parseEpoch: (raw: string | null) => number | "new" | null;
|
|
8
|
+
export declare class RoomReseed {
|
|
9
|
+
#private;
|
|
10
|
+
private readonly room;
|
|
11
|
+
constructor(room: RoomContext);
|
|
12
|
+
/** The re-seed's settings (`null`: off). */
|
|
13
|
+
get config(): {
|
|
14
|
+
at: number;
|
|
15
|
+
idleMs: number;
|
|
16
|
+
gain: number;
|
|
17
|
+
force: number;
|
|
18
|
+
graceDays: number;
|
|
19
|
+
} | null;
|
|
20
|
+
/** The room's epoch: what its document says (`epoch.n`, which only the room writes), 0 before a re-seed. */
|
|
21
|
+
get epoch(): number;
|
|
22
|
+
/**
|
|
23
|
+
* A change was stored at `at` (`room.reseed.timing`): past the
|
|
24
|
+
* threshold the re-seed is due once nobody writes for `idleMs` (each
|
|
25
|
+
* change pushes it back), past `force` at once.
|
|
26
|
+
*/
|
|
27
|
+
noteChange(at: number): void;
|
|
28
|
+
/**
|
|
29
|
+
* The `reseed` task: the cuts past their grace go (`room.reseed.grace`),
|
|
30
|
+
* then it re-seeds when still due (past the threshold and the floor); a
|
|
31
|
+
* re-seed that failed is due again at the first change a minute later or
|
|
32
|
+
* more. It is due again when the oldest cut left expires.
|
|
33
|
+
*/
|
|
34
|
+
task(): void;
|
|
35
|
+
/**
|
|
36
|
+
* Re-seed now (`room.reseed.cut`): the new epoch replaces the rows, in
|
|
37
|
+
* one storage transaction with its cut; the replica registry, the
|
|
38
|
+
* purge's epochs and the last restore's step go (they name the old
|
|
39
|
+
* identities); the live document is rebuilt from the rows and every
|
|
40
|
+
* socket closed `4410`. `idle` (the task, once nobody wrote) waits while
|
|
41
|
+
* a suggestion is pending (an idle period later); `full` (the task, past
|
|
42
|
+
* `force` of the quota) drops them; both do nothing when the new epoch
|
|
43
|
+
* would give back less than `gain` (and wait for the document to grow by
|
|
44
|
+
* a quarter of `at` before trying again). `manual` (`reseed()`) runs
|
|
45
|
+
* whatever it gives back and drops them. Answers the new epoch, or `null`.
|
|
46
|
+
*/
|
|
47
|
+
run(mode: 'idle' | 'full' | 'manual'): number | null;
|
|
48
|
+
/**
|
|
49
|
+
* The door (`RoomAccess.accept`, `room.reseed.protocol`): a dial of
|
|
50
|
+
* epoch `asked` is current, stale (an older epoch whose cuts are kept),
|
|
51
|
+
* or closed: none, in a room that re-seeded, `1008` (a client that
|
|
52
|
+
* predates the re-seed); any other epoch the room cannot rebase (past
|
|
53
|
+
* the cuts kept, or later than its own: the room lost it), `4410`, so
|
|
54
|
+
* the client drops its copy and loads the room's.
|
|
55
|
+
*/
|
|
56
|
+
door(asked: number | 'new' | null): 'current' | 'stale' | {
|
|
57
|
+
code: number;
|
|
58
|
+
reason: string;
|
|
59
|
+
};
|
|
60
|
+
/**
|
|
61
|
+
* Whether `decoded`, from a socket of the current epoch, holds an older
|
|
62
|
+
* epoch's content (`room.reseed.protocol`): a struct of a client the
|
|
63
|
+
* cuts kept name, which the document holds nothing of (a tab took it
|
|
64
|
+
* from another tab of the old epoch). Its socket is closed `4410`.
|
|
65
|
+
*/
|
|
66
|
+
holdsOld(decoded: Decoded, doc: YDoc): boolean;
|
|
67
|
+
/** The largest frame a socket of an older epoch may send: its whole old document. */
|
|
68
|
+
get staleFrameBytes(): number;
|
|
69
|
+
/**
|
|
70
|
+
* A stale socket joined: one that may write is told the room's epoch
|
|
71
|
+
* (its provider then hands over its old document); any other has no
|
|
72
|
+
* edit to merge and is closed `4410`. Past its user's rebase rate it is
|
|
73
|
+
* closed `1011` before it uploads anything: its provider dials again
|
|
74
|
+
* later.
|
|
75
|
+
*/
|
|
76
|
+
join(ws: WebSocket, identity: SocketIdentity): void;
|
|
77
|
+
/**
|
|
78
|
+
* A stale socket's message: its old document (`messageRebase`) is
|
|
79
|
+
* rebased onto a copy of the current one, and what that wrote is
|
|
80
|
+
* admitted as an update of its user, from a client id of its own (the
|
|
81
|
+
* admission's quotas, access, validation and attribution, as any
|
|
82
|
+
* update); then it is closed `4410`. The merged state is kept per user
|
|
83
|
+
* and cut (`merged:<user>`), so another tab's upload merges nothing
|
|
84
|
+
* twice and another user's cannot claim it. A hand-over that cannot be
|
|
85
|
+
* read or rebased is dropped, its socket closed `4410` all the same (its
|
|
86
|
+
* client loads the room's document); past the rebase rate (one per user
|
|
87
|
+
* every ten seconds), `1011`, and the provider hands over again later.
|
|
88
|
+
* Anything else a stale socket sends is dropped.
|
|
89
|
+
*/
|
|
90
|
+
onStale(ws: WebSocket, attachment: Attachment, type: number, decoder: E.Decoder): void;
|
|
91
|
+
/** Drop the cuts older than the grace, with what their rebases merged (`room.reseed.grace`). */
|
|
92
|
+
prune(at: number): void;
|
|
93
|
+
}
|