@pointyink/rogue 0.1.1 → 0.2.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.
@@ -0,0 +1,83 @@
1
+ /**
2
+ * @typedef {{ id: string, author_id: string, start_id: string, end_id: string,
3
+ * replacement: string, status: string, created_at: string, updated_at: string }} Suggestion
4
+ * @typedef {Suggestion & { range: import('./comments.js').DomRange | null }} ResolvedSuggestion
5
+ */
6
+ /**
7
+ * Resolve a suggestion's node-id anchor to a live DOM range with fencepost
8
+ * semantics. Unlike comments, a suggestion brackets a *region*: if a boundary
9
+ * char is edited away, the range steps inward to the nearest surviving visible
10
+ * node (next_vis for the left edge, prev_vis for the right) rather than going
11
+ * stale. Null only when the region between the ids has fully collapsed.
12
+ *
13
+ * @param {import('./comments.js').CrdtSurface} crdt
14
+ * @param {import('./comments.js').RopeSurface} rope
15
+ * @param {string} startId @param {string} endId
16
+ * @returns {import('./comments.js').DomRange | null}
17
+ */
18
+ export function resolveSuggestionAnchor(crdt: import("./comments.js").CrdtSurface, rope: import("./comments.js").RopeSurface, startId: string, endId: string): import("./comments.js").DomRange | null;
19
+ /**
20
+ * @typedef {{
21
+ * crdt: import('./comments.js').CrdtSurface,
22
+ * rope: import('./comments.js').RopeSurface,
23
+ * baseUrl: string,
24
+ * fetchFn?: import('./comments.js').CommentsFetcher,
25
+ * rerender?: (suggestions: ResolvedSuggestion[]) => void,
26
+ * onError?: (error: Error) => void,
27
+ * markUserEdit?: () => void,
28
+ * getSelectionRange?: () => import('./comments.js').DomRange | null,
29
+ * }} SuggestionsOptions
30
+ */
31
+ /**
32
+ * @typedef {{
33
+ * load: () => Promise<ResolvedSuggestion[]>,
34
+ * suggestions: () => ResolvedSuggestion[],
35
+ * create: (replacement: string) => Promise<void>,
36
+ * accept: (suggestionId: string) => Promise<void>,
37
+ * reject: (suggestionId: string) => Promise<void>,
38
+ * handleDocumentChange: (range: { minIx: number, maxIx: number } | null) => void,
39
+ * handleRemoteChange: () => Promise<ResolvedSuggestion[]>,
40
+ * destroy: () => void,
41
+ * }} Suggestions
42
+ */
43
+ /**
44
+ * @param {SuggestionsOptions} options
45
+ * @returns {Suggestions}
46
+ */
47
+ export function createSuggestions({ crdt, rope, baseUrl, fetchFn, rerender, onError, markUserEdit, getSelectionRange }: SuggestionsOptions): Suggestions;
48
+ export type Suggestion = {
49
+ id: string;
50
+ author_id: string;
51
+ start_id: string;
52
+ end_id: string;
53
+ replacement: string;
54
+ status: string;
55
+ created_at: string;
56
+ updated_at: string;
57
+ };
58
+ export type ResolvedSuggestion = Suggestion & {
59
+ range: import("./comments.js").DomRange | null;
60
+ };
61
+ export type SuggestionsOptions = {
62
+ crdt: import("./comments.js").CrdtSurface;
63
+ rope: import("./comments.js").RopeSurface;
64
+ baseUrl: string;
65
+ fetchFn?: import("./comments.js").CommentsFetcher;
66
+ rerender?: (suggestions: ResolvedSuggestion[]) => void;
67
+ onError?: (error: Error) => void;
68
+ markUserEdit?: () => void;
69
+ getSelectionRange?: () => import("./comments.js").DomRange | null;
70
+ };
71
+ export type Suggestions = {
72
+ load: () => Promise<ResolvedSuggestion[]>;
73
+ suggestions: () => ResolvedSuggestion[];
74
+ create: (replacement: string) => Promise<void>;
75
+ accept: (suggestionId: string) => Promise<void>;
76
+ reject: (suggestionId: string) => Promise<void>;
77
+ handleDocumentChange: (range: {
78
+ minIx: number;
79
+ maxIx: number;
80
+ } | null) => void;
81
+ handleRemoteChange: () => Promise<ResolvedSuggestion[]>;
82
+ destroy: () => void;
83
+ };
@@ -2,7 +2,7 @@
2
2
  * @param {SyncClientOptions} options
3
3
  * @returns {SyncClient}
4
4
  */
5
- export function createSyncClient({ wsUrl, docId, mergeOp, getToken, onStatus, onReady }: SyncClientOptions): SyncClient;
5
+ export function createSyncClient({ wsUrl, docId, mergeOp, getToken, onStatus, onReady, onSyncStart, onProgress, onCommentChanged, onSuggestionChanged, onDocDeleted }: SyncClientOptions): SyncClient;
6
6
  export type SyncStatus = "connecting" | "connected" | "disconnected";
7
7
  export type SyncClientOptions = {
8
8
  wsUrl: string;
@@ -11,9 +11,21 @@ export type SyncClientOptions = {
11
11
  getToken: () => Promise<string>;
12
12
  onStatus?: (status: SyncStatus) => void;
13
13
  onReady?: () => void;
14
+ onSyncStart?: (bytes: number) => void;
15
+ onProgress?: (progress: number) => void;
16
+ onCommentChanged?: (info: {
17
+ comment_id: string;
18
+ op: string;
19
+ }) => void;
20
+ onSuggestionChanged?: (info: {
21
+ suggestion_id: string;
22
+ op: string;
23
+ }) => void;
24
+ onDocDeleted?: (docId: string) => void;
14
25
  };
15
26
  export type SyncClient = {
16
27
  send: (payload: Uint8Array) => void;
28
+ sendDelete: () => void;
17
29
  destroy: () => void;
18
30
  isReady: () => boolean;
19
31
  };
package/types/sync.d.ts CHANGED
@@ -1,16 +1,15 @@
1
1
  /**
2
- * Whether an HTML string round-trips through the browser parser unchanged.
3
- * Parsed in an inert <template> (fragment semantics), so context-sensitive
4
- * fragments (<td>, <tr>, <li>) survive where a <div> would drop them — a
5
- * nested/unbalanced tag still comes back different and is caught. Raw innerHTML
6
- * is used instead of the rope+scrub serialization because the CRDT render is
7
- * already clean (scrubbed + escaped), so the two agree, and the template
8
- * round-trip is much cheaper than rebuilding a rope. Exported for direct tests.
2
+ * Whether an HTML string survives a browser-parse round-trip unchanged. `context`
3
+ * is an element whose *tag* supplies the parse context — pass the region's parent
4
+ * so `<td>`/`<tr>`/`<li>` parse in their real context (a bare `<div>` would drop
5
+ * table internals). Omitted → a bare `<div>`. Raw innerHTML is used (not the
6
+ * rope+scrub serialization) because the CRDT render is already clean (scrubbed +
7
+ * escaped). Exported for direct tests.
9
8
  * @param {string} html
10
- * @param {Document} [doc]
9
+ * @param {Element} [context]
11
10
  * @returns {boolean}
12
11
  */
13
- export function roundTrips(html: string, doc?: Document): boolean;
12
+ export function validHtml(html: string, context?: Element): boolean;
14
13
  /**
15
14
  * The vis-index span a reference node contributes to a scoped-sync range, robust
16
15
  * to the DOM churning mid-batch (see localRegion):
@@ -44,6 +43,29 @@ export function nodeSpan(rope: NonNullable<ReturnType<typeof buildRope>>, node:
44
43
  * undo, presence, and cursor attribution key off it — or to coordinate ids
45
44
  * server-side so concurrent replicas never collide (the u16 space is ~65k).
46
45
  * @property {(payload: Uint8Array) => void} [onCommit]
46
+ * @property {number} [maxOpBytes = 8388608] Hard cap on a single committed op
47
+ * batch (one edit = one wire frame). An edit whose inserts exceed this is
48
+ * rejected — `onOpTooLarge` fires and the DOM is reverted to the CRDT — so a
49
+ * pathological paste (or programmatic insert) can't blow the server's read
50
+ * limit. The server (WS_MAX_MESSAGE_SIZE) remains the precise backstop; this
51
+ * guard is the friendly first line for any host, whether or not it uses the
52
+ * engine's own paste listener.
53
+ * @property {(bytes: number, limit: number) => void} [onOpTooLarge] Called when
54
+ * an edit is rejected for exceeding `maxOpBytes`.
55
+ * @property {() => void} [onDocTooLarge] Called when an edit can't be applied
56
+ * because the document has reached its storage capacity. The edit is reverted
57
+ * (the CRDT is left untouched).
58
+ * @property {(error: import('./crdt_error.js').CrdtError) => void} [onError]
59
+ * Called when an operation fails for a reason other than the known limits
60
+ * above — receives the structured error the WASM wrote into its output buffer
61
+ * (`{ type, name, desc? }`, see CrdtErrorKind), so a host can log or toast it.
62
+ * `out_of_memory` on the local commit path continues to route through
63
+ * `onDocTooLarge`.
64
+ * @property {(range: { minIx: number, maxIx: number } | null) => void} [onChange]
65
+ * Called after the document's visible text changes (local edits, remote ops,
66
+ * undo/redo) with the affected vis-index range, or null when it can't be
67
+ * localized. Internal to the rogue layer — comments.js uses it to re-resolve
68
+ * anchored ranges; hosts generally don't need it.
47
69
  * @property {false | { maxBytes?: number, onRejected?: (bytes: number, limit: number) => void }} [paste]
48
70
  * Paste handling. `false` skips the engine's own paste listener — hosts like
49
71
  * TinyMCE have their own paste-clean pipeline, so running both would double-insert.
@@ -76,6 +98,9 @@ export function nodeSpan(rope: NonNullable<ReturnType<typeof buildRope>>, node:
76
98
  * drainParked: () => void,
77
99
  * setSyncDebug: (on: boolean) => void,
78
100
  * markUserEdit: () => void,
101
+ * reserve: (nodes: number) => void,
102
+ * crdt: import('./comments.js').CrdtSurface,
103
+ * rope: import('./dom_rope.js').DomRope,
79
104
  * destroy: () => void,
80
105
  * }} RogueNode
81
106
  */
@@ -83,7 +108,7 @@ export function nodeSpan(rope: NonNullable<ReturnType<typeof buildRope>>, node:
83
108
  * @param {RogueNodeOptions} options
84
109
  * @returns {Promise<RogueNode>}
85
110
  */
86
- export function createRogueNode({ element: editorRoot, replicaId, onCommit, paste, doc, win, serializeNode, omitNode, unwrapNode, }: RogueNodeOptions): Promise<RogueNode>;
111
+ export function createRogueNode({ element: editorRoot, replicaId, onCommit, maxOpBytes, onOpTooLarge, onDocTooLarge, onError, onChange, paste, doc, win, serializeNode, omitNode, unwrapNode, }: RogueNodeOptions): Promise<RogueNode>;
87
112
  export type RogueNodeOptions = {
88
113
  /**
89
114
  * The container the engine renders into and
@@ -101,6 +126,45 @@ export type RogueNodeOptions = {
101
126
  */
102
127
  replicaId?: number | undefined;
103
128
  onCommit?: ((payload: Uint8Array) => void) | undefined;
129
+ /**
130
+ * Hard cap on a single committed op
131
+ * batch (one edit = one wire frame). An edit whose inserts exceed this is
132
+ * rejected — `onOpTooLarge` fires and the DOM is reverted to the CRDT — so a
133
+ * pathological paste (or programmatic insert) can't blow the server's read
134
+ * limit. The server (WS_MAX_MESSAGE_SIZE) remains the precise backstop; this
135
+ * guard is the friendly first line for any host, whether or not it uses the
136
+ * engine's own paste listener.
137
+ */
138
+ maxOpBytes?: number | undefined;
139
+ /**
140
+ * Called when
141
+ * an edit is rejected for exceeding `maxOpBytes`.
142
+ */
143
+ onOpTooLarge?: ((bytes: number, limit: number) => void) | undefined;
144
+ /**
145
+ * Called when an edit can't be applied
146
+ * because the document has reached its storage capacity. The edit is reverted
147
+ * (the CRDT is left untouched).
148
+ */
149
+ onDocTooLarge?: (() => void) | undefined;
150
+ /**
151
+ * Called when an operation fails for a reason other than the known limits
152
+ * above — receives the structured error the WASM wrote into its output buffer
153
+ * (`{ type, name, desc? }`, see CrdtErrorKind), so a host can log or toast it.
154
+ * `out_of_memory` on the local commit path continues to route through
155
+ * `onDocTooLarge`.
156
+ */
157
+ onError?: ((error: import("./crdt_error.js").CrdtError) => void) | undefined;
158
+ /**
159
+ * Called after the document's visible text changes (local edits, remote ops,
160
+ * undo/redo) with the affected vis-index range, or null when it can't be
161
+ * localized. Internal to the rogue layer — comments.js uses it to re-resolve
162
+ * anchored ranges; hosts generally don't need it.
163
+ */
164
+ onChange?: ((range: {
165
+ minIx: number;
166
+ maxIx: number;
167
+ } | null) => void) | undefined;
104
168
  /**
105
169
  * Paste handling. `false` skips the engine's own paste listener — hosts like
106
170
  * TinyMCE have their own paste-clean pipeline, so running both would double-insert.
@@ -149,6 +213,9 @@ export type RogueNode = {
149
213
  drainParked: () => void;
150
214
  setSyncDebug: (on: boolean) => void;
151
215
  markUserEdit: () => void;
216
+ reserve: (nodes: number) => void;
217
+ crdt: import("./comments.js").CrdtSurface;
218
+ rope: import("./dom_rope.js").DomRope;
152
219
  destroy: () => void;
153
220
  };
154
221
  import { buildRope } from "./dom_rope.js";