@dabble/patches 0.2.20 → 0.2.22
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/dist/client/InMemoryStore.d.ts +1 -3
- package/dist/client/InMemoryStore.js +2 -6
- package/dist/client/IndexedDBStore.d.ts +1 -3
- package/dist/client/IndexedDBStore.js +2 -6
- package/dist/client/Patches.js +2 -5
- package/dist/client/PatchesHistoryClient.d.ts +3 -3
- package/dist/client/PatchesHistoryClient.js +4 -4
- package/dist/client/PatchesStore.d.ts +1 -1
- package/dist/net/protocol/JSONRPCServer.d.ts +20 -28
- package/dist/net/protocol/JSONRPCServer.js +44 -60
- package/dist/net/protocol/types.d.ts +6 -0
- package/dist/net/websocket/AuthorizationProvider.d.ts +12 -3
- package/dist/net/websocket/RPCServer.d.ts +120 -0
- package/dist/net/websocket/RPCServer.js +187 -0
- package/dist/net/websocket/WebSocketServer.d.ts +9 -117
- package/dist/net/websocket/WebSocketServer.js +30 -186
- package/dist/server/PatchesServer.d.ts +8 -16
- package/dist/server/PatchesServer.js +15 -24
- package/dist/server/types.d.ts +0 -4
- package/package.json +1 -1
|
@@ -7,13 +7,11 @@ import type { PatchesStore, TrackedDoc } from './PatchesStore.js';
|
|
|
7
7
|
*/
|
|
8
8
|
export declare class InMemoryStore implements PatchesStore {
|
|
9
9
|
private docs;
|
|
10
|
-
/** Signal emitted when pending changes are added (mirrors IndexedDBStore API) */
|
|
11
|
-
readonly onPendingChanges: import("../event-signal.js").Signal<(docId: string, changes: Change[]) => void>;
|
|
12
10
|
getDoc(docId: string): Promise<PatchesSnapshot | undefined>;
|
|
13
11
|
getPendingChanges(docId: string): Promise<Change[]>;
|
|
14
12
|
getLastRevs(docId: string): Promise<[number, number]>;
|
|
15
13
|
listDocs(includeDeleted?: boolean): Promise<TrackedDoc[]>;
|
|
16
|
-
|
|
14
|
+
savePendingChange(docId: string, change: Change): Promise<void>;
|
|
17
15
|
saveCommittedChanges(docId: string, changes: Change[], sentPendingRange?: [number, number]): Promise<void>;
|
|
18
16
|
trackDocs(docIds: string[]): Promise<void>;
|
|
19
17
|
untrackDocs(docIds: string[]): Promise<void>;
|
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
import { signal } from '../event-signal.js';
|
|
2
1
|
import { transformPatch } from '../json-patch/transformPatch.js';
|
|
3
2
|
import { applyChanges } from '../utils.js';
|
|
4
3
|
/**
|
|
@@ -9,8 +8,6 @@ import { applyChanges } from '../utils.js';
|
|
|
9
8
|
export class InMemoryStore {
|
|
10
9
|
constructor() {
|
|
11
10
|
this.docs = new Map();
|
|
12
|
-
/** Signal emitted when pending changes are added (mirrors IndexedDBStore API) */
|
|
13
|
-
this.onPendingChanges = signal();
|
|
14
11
|
}
|
|
15
12
|
// ─── Reconstruction ────────────────────────────────────────────────────
|
|
16
13
|
async getDoc(docId) {
|
|
@@ -55,12 +52,11 @@ export class InMemoryStore {
|
|
|
55
52
|
}));
|
|
56
53
|
}
|
|
57
54
|
// ─── Writes ────────────────────────────────────────────────────────────
|
|
58
|
-
async
|
|
55
|
+
async savePendingChange(docId, change) {
|
|
59
56
|
const buf = this.docs.get(docId) ?? { committed: [], pending: [] };
|
|
60
57
|
if (!this.docs.has(docId))
|
|
61
58
|
this.docs.set(docId, buf);
|
|
62
|
-
buf.pending.push(
|
|
63
|
-
this.onPendingChanges.emit(docId, changes);
|
|
59
|
+
buf.pending.push(change);
|
|
64
60
|
}
|
|
65
61
|
async saveCommittedChanges(docId, changes, sentPendingRange) {
|
|
66
62
|
const buf = this.docs.get(docId) ?? { committed: [], pending: [] };
|
|
@@ -16,8 +16,6 @@ export declare class IndexedDBStore implements PatchesStore {
|
|
|
16
16
|
private db;
|
|
17
17
|
private dbName?;
|
|
18
18
|
private dbPromise;
|
|
19
|
-
/** Subscribe to be notified after local state changes are saved to the database. */
|
|
20
|
-
readonly onPendingChanges: import("../event-signal.js").Signal<(docId: string, changes: Change[]) => void>;
|
|
21
19
|
constructor(dbName?: string);
|
|
22
20
|
private initDB;
|
|
23
21
|
private getDB;
|
|
@@ -54,7 +52,7 @@ export declare class IndexedDBStore implements PatchesStore {
|
|
|
54
52
|
* Append an array of local changes to the pending queue.
|
|
55
53
|
* Called *before* you attempt to send them to the server.
|
|
56
54
|
*/
|
|
57
|
-
|
|
55
|
+
savePendingChange(docId: string, change: Change): Promise<void>;
|
|
58
56
|
/** Read back all pending changes for this docId (in order). */
|
|
59
57
|
getPendingChanges(docId: string): Promise<Change[]>;
|
|
60
58
|
/**
|
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
import { signal } from '../event-signal.js';
|
|
2
1
|
import { transformPatch } from '../json-patch/transformPatch.js';
|
|
3
2
|
import { applyChanges, deferred } from '../utils.js';
|
|
4
3
|
const DB_VERSION = 1;
|
|
@@ -18,8 +17,6 @@ const SNAPSHOT_INTERVAL = 200;
|
|
|
18
17
|
export class IndexedDBStore {
|
|
19
18
|
constructor(dbName) {
|
|
20
19
|
this.db = null;
|
|
21
|
-
/** Subscribe to be notified after local state changes are saved to the database. */
|
|
22
|
-
this.onPendingChanges = signal();
|
|
23
20
|
this.dbName = dbName;
|
|
24
21
|
this.dbPromise = deferred();
|
|
25
22
|
if (this.dbName) {
|
|
@@ -169,7 +166,7 @@ export class IndexedDBStore {
|
|
|
169
166
|
* Append an array of local changes to the pending queue.
|
|
170
167
|
* Called *before* you attempt to send them to the server.
|
|
171
168
|
*/
|
|
172
|
-
async
|
|
169
|
+
async savePendingChange(docId, change) {
|
|
173
170
|
const [tx, pendingChanges, docsStore] = await this.transaction(['pendingChanges', 'docs'], 'readwrite');
|
|
174
171
|
let docMeta = await docsStore.get(docId);
|
|
175
172
|
if (!docMeta) {
|
|
@@ -181,8 +178,7 @@ export class IndexedDBStore {
|
|
|
181
178
|
await docsStore.put(docMeta);
|
|
182
179
|
console.warn(`Revived document ${docId} by saving pending changes.`);
|
|
183
180
|
}
|
|
184
|
-
await
|
|
185
|
-
this.onPendingChanges.emit(docId, changes);
|
|
181
|
+
await pendingChanges.put({ ...change, docId });
|
|
186
182
|
await tx.complete();
|
|
187
183
|
}
|
|
188
184
|
/** Read back all pending changes for this docId (in order). */
|
package/dist/client/Patches.js
CHANGED
|
@@ -207,12 +207,9 @@ export class Patches {
|
|
|
207
207
|
* @returns An Unsubscriber function to remove the listener.
|
|
208
208
|
*/
|
|
209
209
|
_setupLocalDocListener(docId, doc) {
|
|
210
|
-
return doc.onChange(async () => {
|
|
211
|
-
const changes = doc.getUpdatesForServer();
|
|
212
|
-
if (!changes.length)
|
|
213
|
-
return;
|
|
210
|
+
return doc.onChange(async (change) => {
|
|
214
211
|
try {
|
|
215
|
-
await this.store.
|
|
212
|
+
await this.store.savePendingChange(docId, change);
|
|
216
213
|
// Note: When used with PatchesSync, it will handle flushing the changes
|
|
217
214
|
}
|
|
218
215
|
catch (err) {
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import type { PatchesAPI } from '../net/protocol/types.js';
|
|
2
|
-
import type { Change, ListVersionsOptions, VersionMetadata } from '../types.js';
|
|
2
|
+
import type { Change, EditableVersionMetadata, ListVersionsOptions, VersionMetadata } from '../types.js';
|
|
3
3
|
/**
|
|
4
4
|
* Client-side history/scrubbing interface for a document.
|
|
5
5
|
* Read-only: allows listing versions, loading states/changes, and scrubbing.
|
|
@@ -23,9 +23,9 @@ export declare class PatchesHistoryClient<T = any> {
|
|
|
23
23
|
/** List version metadata for this document (with options) */
|
|
24
24
|
listVersions(options?: ListVersionsOptions): Promise<VersionMetadata[]>;
|
|
25
25
|
/** Create a new named version snapshot of the document's current state. */
|
|
26
|
-
createVersion(
|
|
26
|
+
createVersion(metadata: EditableVersionMetadata): Promise<string>;
|
|
27
27
|
/** Update the name of a specific version. */
|
|
28
|
-
updateVersion(versionId: string,
|
|
28
|
+
updateVersion(versionId: string, metadata: EditableVersionMetadata): Promise<void>;
|
|
29
29
|
/** Load the state for a specific version */
|
|
30
30
|
getStateAtVersion(versionId: string): Promise<any>;
|
|
31
31
|
/** Load the changes for a specific version */
|
|
@@ -62,14 +62,14 @@ export class PatchesHistoryClient {
|
|
|
62
62
|
return this._versions;
|
|
63
63
|
}
|
|
64
64
|
/** Create a new named version snapshot of the document's current state. */
|
|
65
|
-
async createVersion(
|
|
66
|
-
const versionId = await this.api.createVersion(this.id,
|
|
65
|
+
async createVersion(metadata) {
|
|
66
|
+
const versionId = await this.api.createVersion(this.id, metadata);
|
|
67
67
|
await this.listVersions(); // Refresh the list of versions
|
|
68
68
|
return versionId;
|
|
69
69
|
}
|
|
70
70
|
/** Update the name of a specific version. */
|
|
71
|
-
async updateVersion(versionId,
|
|
72
|
-
await this.api.updateVersion(this.id, versionId,
|
|
71
|
+
async updateVersion(versionId, metadata) {
|
|
72
|
+
await this.api.updateVersion(this.id, versionId, metadata);
|
|
73
73
|
await this.listVersions(); // Refresh the list of versions
|
|
74
74
|
}
|
|
75
75
|
/** Load the state for a specific version */
|
|
@@ -27,7 +27,7 @@ export interface PatchesStore {
|
|
|
27
27
|
getDoc(docId: string): Promise<PatchesSnapshot | undefined>;
|
|
28
28
|
getPendingChanges(docId: string): Promise<Change[]>;
|
|
29
29
|
getLastRevs(docId: string): Promise<[committedRev: number, pendingRev: number]>;
|
|
30
|
-
|
|
30
|
+
savePendingChange(docId: string, change: Change): Promise<void>;
|
|
31
31
|
saveCommittedChanges(docId: string, changes: Change[], sentPendingRange?: [number, number]): Promise<void>;
|
|
32
32
|
/** Permanently delete document (writes tombstone so server delete happens later). */
|
|
33
33
|
deleteDoc(docId: string): Promise<void>;
|
|
@@ -1,7 +1,8 @@
|
|
|
1
|
-
import { type Unsubscriber } from '../../event-signal.js';
|
|
2
|
-
import type {
|
|
3
|
-
|
|
4
|
-
export type
|
|
1
|
+
import { type Signal, type Unsubscriber } from '../../event-signal.js';
|
|
2
|
+
import type { AuthContext } from '../websocket/AuthorizationProvider.js';
|
|
3
|
+
import type { Notification } from './types.js';
|
|
4
|
+
export type ConnectionSignalSubscriber = (params: any, clientId?: string) => any;
|
|
5
|
+
export type MessageHandler<P = any, R = any> = (params: P, ctx?: AuthContext) => Promise<R> | R;
|
|
5
6
|
/**
|
|
6
7
|
* Lightweight JSON-RPC 2.0 server adapter for {@link PatchesServer}.
|
|
7
8
|
*
|
|
@@ -19,12 +20,12 @@ export type MessageHandler<P = any, R = any> = (connectionId: string, params: P)
|
|
|
19
20
|
* to the host application.
|
|
20
21
|
*/
|
|
21
22
|
export declare class JSONRPCServer {
|
|
22
|
-
protected transport: ServerTransport;
|
|
23
23
|
/** Map of fully-qualified JSON-RPC method → handler function */
|
|
24
24
|
private readonly handlers;
|
|
25
25
|
/** Allow external callers to emit server-initiated notifications. */
|
|
26
26
|
private readonly notificationSignals;
|
|
27
|
-
|
|
27
|
+
/** Allow external callers to emit server-initiated notifications. */
|
|
28
|
+
readonly onNotify: Signal<(msg: Notification, exceptConnectionId?: string) => void>;
|
|
28
29
|
/**
|
|
29
30
|
* Registers a JSON-RPC method.
|
|
30
31
|
*
|
|
@@ -45,25 +46,20 @@ export declare class JSONRPCServer {
|
|
|
45
46
|
* Sends a JSON-RPC notification (no `id`, therefore no response expected) to
|
|
46
47
|
* the connected client.
|
|
47
48
|
*/
|
|
48
|
-
notify(
|
|
49
|
-
/**
|
|
50
|
-
* Handles incoming messages from the client.
|
|
51
|
-
* @param connectionId - The WebSocket transport object.
|
|
52
|
-
* @param raw - The raw message string.
|
|
53
|
-
*/
|
|
54
|
-
protected _onMessage(connectionId: string, raw: string): Promise<void>;
|
|
55
|
-
/**
|
|
56
|
-
* Handles incoming JSON-RPC requests from the client.
|
|
57
|
-
* @param connectionId - The WebSocket transport object.
|
|
58
|
-
* @param req - The JSON-RPC request object.
|
|
59
|
-
*/
|
|
60
|
-
protected _handleRequest(connectionId: string, req: Request): Promise<void>;
|
|
49
|
+
notify(method: string, params?: any, exceptConnectionId?: string): Promise<void>;
|
|
61
50
|
/**
|
|
62
|
-
*
|
|
63
|
-
*
|
|
64
|
-
*
|
|
51
|
+
* Synchronously processes a raw JSON-RPC frame from a client and returns the
|
|
52
|
+
* encoded response frame – or `undefined` when the message is a notification
|
|
53
|
+
* (no response expected).
|
|
54
|
+
*
|
|
55
|
+
* This helper makes the RPC engine usable for stateless transports such as
|
|
56
|
+
* HTTP: the host simply passes the request body and sends back the returned
|
|
57
|
+
* string (if any).
|
|
58
|
+
*
|
|
59
|
+
* WebSocket and other bidirectional transports delegate to the same logic
|
|
60
|
+
* internally; the returned string is forwarded over the socket.
|
|
65
61
|
*/
|
|
66
|
-
|
|
62
|
+
processMessage(raw: string, ctx?: AuthContext): Promise<string | undefined>;
|
|
67
63
|
/**
|
|
68
64
|
* Maps JSON-RPC method names to {@link PatchesServer} calls.
|
|
69
65
|
* @param connectionId - The WebSocket transport object.
|
|
@@ -71,9 +67,5 @@ export declare class JSONRPCServer {
|
|
|
71
67
|
* @param params - The JSON-RPC parameters.
|
|
72
68
|
* @returns The result of the {@link PatchesServer} call.
|
|
73
69
|
*/
|
|
74
|
-
protected _dispatch(
|
|
75
|
-
/**
|
|
76
|
-
* Sends a JSON-RPC error object back to the client.
|
|
77
|
-
*/
|
|
78
|
-
private _sendError;
|
|
70
|
+
protected _dispatch(method: string, params: any, ctx?: AuthContext): Promise<any>;
|
|
79
71
|
}
|
|
@@ -1,5 +1,4 @@
|
|
|
1
1
|
import { signal } from '../../event-signal.js';
|
|
2
|
-
import { PatchesServer } from '../../server/PatchesServer.js';
|
|
3
2
|
/**
|
|
4
3
|
* Lightweight JSON-RPC 2.0 server adapter for {@link PatchesServer}.
|
|
5
4
|
*
|
|
@@ -17,13 +16,13 @@ import { PatchesServer } from '../../server/PatchesServer.js';
|
|
|
17
16
|
* to the host application.
|
|
18
17
|
*/
|
|
19
18
|
export class JSONRPCServer {
|
|
20
|
-
constructor(
|
|
21
|
-
this.transport = transport;
|
|
19
|
+
constructor() {
|
|
22
20
|
/** Map of fully-qualified JSON-RPC method → handler function */
|
|
23
21
|
this.handlers = new Map();
|
|
24
22
|
/** Allow external callers to emit server-initiated notifications. */
|
|
25
23
|
this.notificationSignals = new Map();
|
|
26
|
-
|
|
24
|
+
/** Allow external callers to emit server-initiated notifications. */
|
|
25
|
+
this.onNotify = signal();
|
|
27
26
|
}
|
|
28
27
|
// -------------------------------------------------------------------------
|
|
29
28
|
// Registration API
|
|
@@ -63,63 +62,56 @@ export class JSONRPCServer {
|
|
|
63
62
|
* Sends a JSON-RPC notification (no `id`, therefore no response expected) to
|
|
64
63
|
* the connected client.
|
|
65
64
|
*/
|
|
66
|
-
notify(
|
|
65
|
+
async notify(method, params, exceptConnectionId) {
|
|
67
66
|
const msg = { jsonrpc: '2.0', method, params };
|
|
68
|
-
|
|
69
|
-
connectionIds.forEach(id => this.transport.send(id, msgStr));
|
|
67
|
+
this.onNotify.emit(msg, exceptConnectionId);
|
|
70
68
|
}
|
|
71
69
|
/**
|
|
72
|
-
*
|
|
73
|
-
*
|
|
74
|
-
*
|
|
70
|
+
* Synchronously processes a raw JSON-RPC frame from a client and returns the
|
|
71
|
+
* encoded response frame – or `undefined` when the message is a notification
|
|
72
|
+
* (no response expected).
|
|
73
|
+
*
|
|
74
|
+
* This helper makes the RPC engine usable for stateless transports such as
|
|
75
|
+
* HTTP: the host simply passes the request body and sends back the returned
|
|
76
|
+
* string (if any).
|
|
77
|
+
*
|
|
78
|
+
* WebSocket and other bidirectional transports delegate to the same logic
|
|
79
|
+
* internally; the returned string is forwarded over the socket.
|
|
75
80
|
*/
|
|
76
|
-
async
|
|
81
|
+
async processMessage(raw, ctx) {
|
|
77
82
|
let message;
|
|
83
|
+
// --- Parse & basic validation ------------------------------------------------
|
|
78
84
|
try {
|
|
79
85
|
message = JSON.parse(raw);
|
|
80
86
|
}
|
|
81
87
|
catch (err) {
|
|
82
|
-
|
|
83
|
-
|
|
88
|
+
return rpcError(-32700, 'Parse error', err);
|
|
89
|
+
}
|
|
90
|
+
// Ensure it looks like a JSON-RPC call (must have a method field)
|
|
91
|
+
if (!message || typeof message !== 'object' || !('method' in message)) {
|
|
92
|
+
const invalidId = message?.id ?? null;
|
|
93
|
+
return rpcError(-32600, 'Invalid Request', invalidId);
|
|
84
94
|
}
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
95
|
+
// --- Distinguish request vs. notification -----------------------------------
|
|
96
|
+
if ('id' in message && message.id !== undefined) {
|
|
97
|
+
// -> Request ----------------------------------------------------------------
|
|
98
|
+
try {
|
|
99
|
+
const result = await this._dispatch(message.method, message.params, ctx);
|
|
100
|
+
const response = { jsonrpc: '2.0', id: message.id, result };
|
|
101
|
+
return JSON.stringify(response);
|
|
89
102
|
}
|
|
90
|
-
|
|
91
|
-
|
|
103
|
+
catch (err) {
|
|
104
|
+
return rpcError(err?.code ?? -32000, err?.message ?? 'Server error', err?.stack);
|
|
92
105
|
}
|
|
93
106
|
}
|
|
94
107
|
else {
|
|
95
|
-
//
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
* @param req - The JSON-RPC request object.
|
|
103
|
-
*/
|
|
104
|
-
async _handleRequest(connectionId, req) {
|
|
105
|
-
try {
|
|
106
|
-
const result = await this._dispatch(connectionId, req.method, req.params);
|
|
107
|
-
const response = { jsonrpc: '2.0', id: req.id, result };
|
|
108
|
-
this.transport.send(connectionId, JSON.stringify(response));
|
|
109
|
-
}
|
|
110
|
-
catch (err) {
|
|
111
|
-
this._sendError(connectionId, req.id, -32000, err?.message ?? 'Server error', err?.stack);
|
|
112
|
-
}
|
|
113
|
-
}
|
|
114
|
-
/**
|
|
115
|
-
* Handles incoming JSON-RPC notifications from the client.
|
|
116
|
-
* @param connectionId - The WebSocket transport object.
|
|
117
|
-
* @param note - The JSON-RPC notification object.
|
|
118
|
-
*/
|
|
119
|
-
async _handleNotification(connectionId, note) {
|
|
120
|
-
const thisSignal = this.notificationSignals.get(note.method);
|
|
121
|
-
if (thisSignal) {
|
|
122
|
-
thisSignal.emit(connectionId, note.params);
|
|
108
|
+
// -> Notification -----------------------------------------------------------
|
|
109
|
+
// Forward the notification to any listeners and return nothing.
|
|
110
|
+
const thisSignal = this.notificationSignals.get(message.method);
|
|
111
|
+
if (thisSignal) {
|
|
112
|
+
thisSignal.emit(message.params, ctx?.clientId);
|
|
113
|
+
}
|
|
114
|
+
return undefined;
|
|
123
115
|
}
|
|
124
116
|
}
|
|
125
117
|
/**
|
|
@@ -129,7 +121,7 @@ export class JSONRPCServer {
|
|
|
129
121
|
* @param params - The JSON-RPC parameters.
|
|
130
122
|
* @returns The result of the {@link PatchesServer} call.
|
|
131
123
|
*/
|
|
132
|
-
async _dispatch(
|
|
124
|
+
async _dispatch(method, params, ctx) {
|
|
133
125
|
const handler = this.handlers.get(method);
|
|
134
126
|
if (!handler) {
|
|
135
127
|
throw new Error(`Unknown method '${method}'.`);
|
|
@@ -137,17 +129,9 @@ export class JSONRPCServer {
|
|
|
137
129
|
if (!params || typeof params !== 'object' || Array.isArray(params)) {
|
|
138
130
|
throw new Error(`Invalid parameters for method '${method}'.`);
|
|
139
131
|
}
|
|
140
|
-
return handler(
|
|
141
|
-
}
|
|
142
|
-
/**
|
|
143
|
-
* Sends a JSON-RPC error object back to the client.
|
|
144
|
-
*/
|
|
145
|
-
_sendError(connectionId, id, code, message, data) {
|
|
146
|
-
const errorObj = {
|
|
147
|
-
jsonrpc: '2.0',
|
|
148
|
-
id: id,
|
|
149
|
-
error: { code, message, data },
|
|
150
|
-
}; // type cast because TS cannot narrow when error present
|
|
151
|
-
this.transport.send(connectionId, JSON.stringify(errorObj));
|
|
132
|
+
return handler(params, ctx);
|
|
152
133
|
}
|
|
153
134
|
}
|
|
135
|
+
function rpcError(code, message, data) {
|
|
136
|
+
return JSON.stringify({ jsonrpc: '2.0', id: null, error: { code, message, data } });
|
|
137
|
+
}
|
|
@@ -39,6 +39,12 @@ export interface ServerTransport {
|
|
|
39
39
|
send(toConnectionId: string, raw: string): void | Promise<void>;
|
|
40
40
|
/** Subscribe to incoming raw frames from any client */
|
|
41
41
|
onMessage(cb: (fromConnectionId: string, raw: string) => void): Unsubscriber;
|
|
42
|
+
/** Lists all subscriptions for a document. */
|
|
43
|
+
listSubscriptions(docId: string): Promise<string[]>;
|
|
44
|
+
/** Adds a subscription for a client to one or more documents. */
|
|
45
|
+
addSubscription(clientId: string, docIds: string[]): Promise<string[]>;
|
|
46
|
+
/** Removes a subscription for a client from one or more documents. */
|
|
47
|
+
removeSubscription(clientId: string, docIds: string[]): Promise<string[]>;
|
|
42
48
|
}
|
|
43
49
|
/**
|
|
44
50
|
* Represents a JSON-RPC 2.0 request object.
|
|
@@ -5,13 +5,22 @@
|
|
|
5
5
|
* "write" – mutating operations (commitChanges, deleteDoc, …)
|
|
6
6
|
*/
|
|
7
7
|
export type Access = 'read' | 'write';
|
|
8
|
+
/**
|
|
9
|
+
* Context object for authorization providers.
|
|
10
|
+
* @property {string} clientId - The ID of the client making the request.
|
|
11
|
+
* @property {any} [data] - Additional data associated with the request.
|
|
12
|
+
*/
|
|
13
|
+
export interface AuthContext {
|
|
14
|
+
clientId?: string;
|
|
15
|
+
[k: string]: any;
|
|
16
|
+
}
|
|
8
17
|
/**
|
|
9
18
|
* Allows the host application to decide whether a given connection can perform
|
|
10
19
|
* a certain action on a document. Implementations are entirely application-
|
|
11
20
|
* specific – they may look at a JWT decoded during the WebSocket handshake,
|
|
12
21
|
* consult an ACL service, inspect the actual RPC method, etc.
|
|
13
22
|
*/
|
|
14
|
-
export interface AuthorizationProvider {
|
|
23
|
+
export interface AuthorizationProvider<T extends AuthContext = AuthContext> {
|
|
15
24
|
/**
|
|
16
25
|
* General-purpose hook executed for every JSON-RPC call that targets a
|
|
17
26
|
* document. Implementations are free to look only at the first three
|
|
@@ -22,13 +31,13 @@ export interface AuthorizationProvider {
|
|
|
22
31
|
* Returning `true` (or a resolved promise with `true`) permits the action.
|
|
23
32
|
* Returning `false` or throwing will cause the RPC to fail with an error.
|
|
24
33
|
*
|
|
25
|
-
* @param
|
|
34
|
+
* @param ctx Context object containing client ID and additional data
|
|
26
35
|
* @param docId Logical document to be accessed (branch IDs count)
|
|
27
36
|
* @param kind High-level access category – `'read' | 'write'`
|
|
28
37
|
* @param method JSON-RPC method name (e.g. `getDoc`, `branch.merge`)
|
|
29
38
|
* @param params The exact parameter object supplied by the client
|
|
30
39
|
*/
|
|
31
|
-
canAccess(
|
|
40
|
+
canAccess(ctx: T | undefined, docId: string, kind: Access, method: string, params?: Record<string, any>): boolean | Promise<boolean>;
|
|
32
41
|
}
|
|
33
42
|
/**
|
|
34
43
|
* A permissive provider that authorises every action. Used as the default so
|
|
@@ -0,0 +1,120 @@
|
|
|
1
|
+
import type { PatchesBranchManager } from '../../server/PatchesBranchManager.js';
|
|
2
|
+
import type { PatchesHistoryManager } from '../../server/PatchesHistoryManager.js';
|
|
3
|
+
import type { PatchesServer } from '../../server/PatchesServer.js';
|
|
4
|
+
import type { Change, EditableVersionMetadata, ListChangesOptions, ListVersionsOptions } from '../../types.js';
|
|
5
|
+
import { JSONRPCServer } from '../protocol/JSONRPCServer.js';
|
|
6
|
+
import type { ServerTransport } from '../protocol/types.js';
|
|
7
|
+
import type { AuthContext, AuthorizationProvider } from './AuthorizationProvider.js';
|
|
8
|
+
/**
|
|
9
|
+
* High-level client for the Patches real-time collaboration service.
|
|
10
|
+
* This class provides document subscription, patch notification handling,
|
|
11
|
+
* versioning, and other OT-specific functionality over a JSON RPC interface.
|
|
12
|
+
*/
|
|
13
|
+
export interface RPCServerOptions {
|
|
14
|
+
transport: ServerTransport;
|
|
15
|
+
patches: PatchesServer;
|
|
16
|
+
history?: PatchesHistoryManager;
|
|
17
|
+
branches?: PatchesBranchManager;
|
|
18
|
+
auth?: AuthorizationProvider;
|
|
19
|
+
}
|
|
20
|
+
export declare class RPCServer {
|
|
21
|
+
rpc: JSONRPCServer;
|
|
22
|
+
auth: AuthorizationProvider;
|
|
23
|
+
protected patches: PatchesServer;
|
|
24
|
+
protected history?: PatchesHistoryManager;
|
|
25
|
+
protected branches?: PatchesBranchManager;
|
|
26
|
+
/**
|
|
27
|
+
* Creates a new Patches WebSocket client instance.
|
|
28
|
+
* @param patches - The patches server instance to handle document operations
|
|
29
|
+
* @param history - (Optional) History manager instance to handle versioning operations
|
|
30
|
+
* @param branches - (Optional) Branch manager instance to handle branching operations
|
|
31
|
+
* @param auth - (Optional) Authorization provider implementation. Defaults to a permissive provider.
|
|
32
|
+
*/
|
|
33
|
+
constructor({ patches, history, branches, auth }: RPCServerOptions);
|
|
34
|
+
/**
|
|
35
|
+
* Gets the latest state (content and revision) of a document.
|
|
36
|
+
* @param connectionId - The ID of the connection making the request
|
|
37
|
+
* @param params - The document parameters
|
|
38
|
+
* @param params.docId - The ID of the document
|
|
39
|
+
* @param params.atRev - Optional revision number to get document state at
|
|
40
|
+
*/
|
|
41
|
+
getDoc(params: {
|
|
42
|
+
docId: string;
|
|
43
|
+
atRev?: number;
|
|
44
|
+
}, ctx?: AuthContext): Promise<import("../../types.js").PatchesSnapshot<any>>;
|
|
45
|
+
/**
|
|
46
|
+
* Gets changes that occurred for a document after a specific revision number.
|
|
47
|
+
* @param connectionId - The ID of the connection making the request
|
|
48
|
+
* @param params - The change request parameters
|
|
49
|
+
* @param params.docId - The ID of the document
|
|
50
|
+
* @param params.rev - The revision number after which to fetch changes
|
|
51
|
+
*/
|
|
52
|
+
getChangesSince(params: {
|
|
53
|
+
docId: string;
|
|
54
|
+
rev: number;
|
|
55
|
+
}, ctx?: AuthContext): Promise<Change[]>;
|
|
56
|
+
/**
|
|
57
|
+
* Applies a set of client-generated changes to a document on the server.
|
|
58
|
+
* @param connectionId - The ID of the connection making the request
|
|
59
|
+
* @param params - The change parameters
|
|
60
|
+
* @param params.docId - The ID of the document
|
|
61
|
+
* @param params.changes - An array of changes to apply
|
|
62
|
+
*/
|
|
63
|
+
commitChanges(params: {
|
|
64
|
+
docId: string;
|
|
65
|
+
changes: Change[];
|
|
66
|
+
}, ctx?: AuthContext): Promise<Change[]>;
|
|
67
|
+
/**
|
|
68
|
+
* Deletes a document on the server.
|
|
69
|
+
* @param connectionId - The ID of the connection making the request
|
|
70
|
+
* @param params - The deletion parameters
|
|
71
|
+
* @param params.docId - The ID of the document to delete
|
|
72
|
+
*/
|
|
73
|
+
deleteDoc(params: {
|
|
74
|
+
docId: string;
|
|
75
|
+
}, ctx?: AuthContext): Promise<void>;
|
|
76
|
+
listVersions(params: {
|
|
77
|
+
docId: string;
|
|
78
|
+
options?: ListVersionsOptions;
|
|
79
|
+
}, ctx?: AuthContext): Promise<import("../../types.js").VersionMetadata[]>;
|
|
80
|
+
createVersion(params: {
|
|
81
|
+
docId: string;
|
|
82
|
+
metadata: EditableVersionMetadata;
|
|
83
|
+
}, ctx?: AuthContext): Promise<string>;
|
|
84
|
+
updateVersion(params: {
|
|
85
|
+
docId: string;
|
|
86
|
+
versionId: string;
|
|
87
|
+
metadata: EditableVersionMetadata;
|
|
88
|
+
}, ctx?: AuthContext): Promise<void>;
|
|
89
|
+
getStateAtVersion(params: {
|
|
90
|
+
docId: string;
|
|
91
|
+
versionId: string;
|
|
92
|
+
}, ctx?: AuthContext): Promise<any>;
|
|
93
|
+
getChangesForVersion(params: {
|
|
94
|
+
docId: string;
|
|
95
|
+
versionId: string;
|
|
96
|
+
}, ctx?: AuthContext): Promise<Change[]>;
|
|
97
|
+
listServerChanges(params: {
|
|
98
|
+
docId: string;
|
|
99
|
+
options?: ListChangesOptions;
|
|
100
|
+
}, ctx?: AuthContext): Promise<Change[]>;
|
|
101
|
+
listBranches(params: {
|
|
102
|
+
docId: string;
|
|
103
|
+
}, ctx?: AuthContext): Promise<import("../../types.js").Branch[]>;
|
|
104
|
+
createBranch(params: {
|
|
105
|
+
docId: string;
|
|
106
|
+
rev: number;
|
|
107
|
+
metadata?: EditableVersionMetadata;
|
|
108
|
+
}, ctx?: AuthContext): Promise<string>;
|
|
109
|
+
closeBranch(params: {
|
|
110
|
+
branchId: string;
|
|
111
|
+
}, ctx?: AuthContext): Promise<void>;
|
|
112
|
+
mergeBranch(params: {
|
|
113
|
+
branchId: string;
|
|
114
|
+
}, ctx?: AuthContext): Promise<Change[]>;
|
|
115
|
+
protected assertAccess(ctx: AuthContext | undefined, docId: string, kind: 'read' | 'write', method: string, params?: Record<string, any>): Promise<void>;
|
|
116
|
+
protected assertRead(ctx: AuthContext | undefined, docId: string, method: string, params?: Record<string, any>): Promise<void>;
|
|
117
|
+
protected assertWrite(ctx: AuthContext | undefined, docId: string, method: string, params?: Record<string, any>): Promise<void>;
|
|
118
|
+
protected assertHistoryEnabled(): void;
|
|
119
|
+
protected assertBranchingEnabled(): void;
|
|
120
|
+
}
|