@adonis-agora/collaboration 0.1.4 → 0.2.1
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/CHANGELOG.md +24 -0
- package/README.md +172 -0
- package/dist/commands/collaboration_init.d.ts +26 -0
- package/dist/commands/collaboration_init.d.ts.map +1 -0
- package/dist/commands/collaboration_init.js +83 -0
- package/dist/commands/collaboration_init.js.map +1 -0
- package/dist/commands/commands.json +1 -0
- package/dist/commands/main.d.ts +16 -0
- package/dist/commands/main.d.ts.map +1 -0
- package/dist/commands/main.js +20 -0
- package/dist/commands/main.js.map +1 -0
- package/dist/commands/make_collab_document.d.ts +23 -0
- package/dist/commands/make_collab_document.d.ts.map +1 -0
- package/dist/commands/make_collab_document.js +75 -0
- package/dist/commands/make_collab_document.js.map +1 -0
- package/dist/configure.d.ts +3 -1
- package/dist/configure.d.ts.map +1 -1
- package/dist/configure.js +6 -1
- package/dist/configure.js.map +1 -1
- package/dist/providers/collaboration_provider.d.ts +6 -2
- package/dist/providers/collaboration_provider.d.ts.map +1 -1
- package/dist/providers/collaboration_provider.js +36 -17
- package/dist/providers/collaboration_provider.js.map +1 -1
- package/dist/src/auth/token.d.ts +59 -0
- package/dist/src/auth/token.d.ts.map +1 -0
- package/dist/src/auth/token.js +91 -0
- package/dist/src/auth/token.js.map +1 -0
- package/dist/src/codegen/documents_registry.d.ts +28 -0
- package/dist/src/codegen/documents_registry.d.ts.map +1 -0
- package/dist/src/codegen/documents_registry.js +50 -0
- package/dist/src/codegen/documents_registry.js.map +1 -0
- package/dist/src/codegen_init.d.ts +3 -0
- package/dist/src/codegen_init.d.ts.map +1 -0
- package/dist/src/codegen_init.js +45 -0
- package/dist/src/codegen_init.js.map +1 -0
- package/dist/src/collaboration_manager.d.ts +20 -11
- package/dist/src/collaboration_manager.d.ts.map +1 -1
- package/dist/src/collaboration_manager.js +46 -17
- package/dist/src/collaboration_manager.js.map +1 -1
- package/dist/src/define_config.d.ts +25 -6
- package/dist/src/define_config.d.ts.map +1 -1
- package/dist/src/define_config.js +2 -2
- package/dist/src/define_config.js.map +1 -1
- package/dist/src/driver.d.ts +26 -32
- package/dist/src/driver.d.ts.map +1 -1
- package/dist/src/drivers/automerge/automerge_driver.d.ts +7 -12
- package/dist/src/drivers/automerge/automerge_driver.d.ts.map +1 -1
- package/dist/src/drivers/automerge/automerge_driver.js +37 -75
- package/dist/src/drivers/automerge/automerge_driver.js.map +1 -1
- package/dist/src/drivers/partykit/partykit_driver.d.ts +54 -0
- package/dist/src/drivers/partykit/partykit_driver.d.ts.map +1 -0
- package/dist/src/drivers/partykit/partykit_driver.js +162 -0
- package/dist/src/drivers/partykit/partykit_driver.js.map +1 -0
- package/dist/src/drivers/shared.d.ts +17 -0
- package/dist/src/drivers/shared.d.ts.map +1 -0
- package/dist/src/drivers/shared.js +46 -0
- package/dist/src/drivers/shared.js.map +1 -0
- package/dist/src/drivers/yjs/yjs_driver.d.ts +18 -12
- package/dist/src/drivers/yjs/yjs_driver.d.ts.map +1 -1
- package/dist/src/drivers/yjs/yjs_driver.js +50 -94
- package/dist/src/drivers/yjs/yjs_driver.js.map +1 -1
- package/dist/src/http/types.d.ts +79 -0
- package/dist/src/http/types.d.ts.map +1 -0
- package/dist/src/http/types.js +8 -0
- package/dist/src/http/types.js.map +1 -0
- package/dist/src/index.d.ts +7 -3
- package/dist/src/index.d.ts.map +1 -1
- package/dist/src/index.js +7 -2
- package/dist/src/index.js.map +1 -1
- package/dist/src/routes.d.ts +29 -0
- package/dist/src/routes.d.ts.map +1 -0
- package/dist/src/routes.js +189 -0
- package/dist/src/routes.js.map +1 -0
- package/dist/src/services/booted_app.js +1 -1
- package/dist/src/services/booted_app.js.map +1 -1
- package/dist/src/services/comment_service.d.ts +5 -4
- package/dist/src/services/comment_service.d.ts.map +1 -1
- package/dist/src/services/comment_service.js +5 -4
- package/dist/src/services/comment_service.js.map +1 -1
- package/dist/src/services/main.js +2 -2
- package/dist/src/services/main.js.map +1 -1
- package/dist/src/storage/file_system_storage.d.ts +2 -2
- package/dist/src/storage/file_system_storage.js +2 -2
- package/dist/src/storage/in_memory_storage.d.ts +3 -3
- package/dist/src/storage/in_memory_storage.js +3 -3
- package/dist/src/storage/lucid_storage.d.ts +9 -8
- package/dist/src/storage/lucid_storage.d.ts.map +1 -1
- package/dist/src/storage/lucid_storage.js +13 -12
- package/dist/src/storage/lucid_storage.js.map +1 -1
- package/dist/src/types.d.ts +62 -35
- package/dist/src/types.d.ts.map +1 -1
- package/dist/src/types.js +5 -6
- package/dist/src/types.js.map +1 -1
- package/dist/src/versioning.d.ts +3 -3
- package/dist/src/versioning.d.ts.map +1 -1
- package/dist/src/versioning.js +3 -3
- package/dist/src/versioning.js.map +1 -1
- package/dist/stubs/collaboration/document_types.stub +41 -0
- package/dist/stubs/collaboration/documents_dir.stub +3 -0
- package/dist/stubs/collaboration/migrations/collab_comments.stub +29 -0
- package/dist/stubs/collaboration/migrations/collab_documents.stub +23 -0
- package/dist/stubs/collaboration/migrations/collab_versions.stub +26 -0
- package/dist/stubs/partykit/worker/package.json.stub +21 -0
- package/dist/stubs/partykit/worker/party.ts.stub +175 -0
- package/dist/stubs/partykit/worker/partykit.toml.stub +14 -0
- package/package.json +19 -4
- package/dist/src/services/presence_service.d.ts +0 -44
- package/dist/src/services/presence_service.d.ts.map +0 -1
- package/dist/src/services/presence_service.js +0 -103
- package/dist/src/services/presence_service.js.map +0 -1
|
@@ -0,0 +1,162 @@
|
|
|
1
|
+
import { TiptapTransformer } from '@hocuspocus/transformer';
|
|
2
|
+
import * as Y from 'yjs';
|
|
3
|
+
import { createVersionMetadata, seqVersions } from '../../versioning.js';
|
|
4
|
+
import { lineDiff, resolveStorage, tiptapJsonToText } from '../shared.js';
|
|
5
|
+
/**
|
|
6
|
+
* PartyKit driver (edge sync).
|
|
7
|
+
*
|
|
8
|
+
* Unlike the Yjs/Automerge drivers, the transport is NOT attached to the
|
|
9
|
+
* Adonis HTTP server: PartyKit runs as a standalone Worker (Cloudflare
|
|
10
|
+
* Durable Objects) on its own origin and browsers connect to it directly.
|
|
11
|
+
* This driver is the worker's HTTP client — `attach()` is an intentional
|
|
12
|
+
* no-op.
|
|
13
|
+
*
|
|
14
|
+
* Responsibilities:
|
|
15
|
+
* - `getDocumentState`/`getDocumentText`: GET the worker's `onRequest`;
|
|
16
|
+
* - `restoreVersion`: POST the binary update to the live doc (edge broadcast);
|
|
17
|
+
* - version control + comments: stay in Adonis (injected storage),
|
|
18
|
+
* fetching the current room state over HTTP before snapshotting.
|
|
19
|
+
*
|
|
20
|
+
* The worker template (`stubs/partykit/worker/`) is published by
|
|
21
|
+
* `collaboration:init --engine=partykit`.
|
|
22
|
+
*/
|
|
23
|
+
export { signPartyKitToken, verifyPartyKitToken } from '../../auth/token.js';
|
|
24
|
+
/** Typed error for non-2xx worker responses. */
|
|
25
|
+
export class PartyKitRoomError extends Error {
|
|
26
|
+
status;
|
|
27
|
+
body;
|
|
28
|
+
docName;
|
|
29
|
+
constructor(status, body, docName) {
|
|
30
|
+
super(`PartyKit room "${docName}" responded ${status}: ${body}`);
|
|
31
|
+
this.status = status;
|
|
32
|
+
this.body = body;
|
|
33
|
+
this.docName = docName;
|
|
34
|
+
this.name = 'PartyKitRoomError';
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
export class PartyKitDriver {
|
|
38
|
+
engine = 'partykit';
|
|
39
|
+
options;
|
|
40
|
+
storage;
|
|
41
|
+
constructor(options) {
|
|
42
|
+
this.options = options;
|
|
43
|
+
this.storage = resolveStorage(options.storage);
|
|
44
|
+
}
|
|
45
|
+
/** Validates the partykit config (throws a clear message when absent). */
|
|
46
|
+
partyConfig() {
|
|
47
|
+
const pk = this.options.partykit;
|
|
48
|
+
if (!pk?.roomHost) {
|
|
49
|
+
throw new Error("[@adonis-agora/collaboration] engine 'partykit' requires config.partykit.roomHost " +
|
|
50
|
+
'(the deployment host, e.g. my-app.partykit.dev)');
|
|
51
|
+
}
|
|
52
|
+
return pk;
|
|
53
|
+
}
|
|
54
|
+
/** Room HTTP endpoint URL on the worker. */
|
|
55
|
+
roomUrl(docName) {
|
|
56
|
+
const pk = this.partyConfig();
|
|
57
|
+
const host = pk.roomHost.replace(/^https?:\/\//, '');
|
|
58
|
+
const scheme = host.startsWith('localhost') || host.startsWith('127.0.0.1') ? 'http' : 'https';
|
|
59
|
+
return `${scheme}://${host}/parties/${pk.party ?? 'main'}/${encodeURIComponent(docName)}`;
|
|
60
|
+
}
|
|
61
|
+
/** Authenticated HTTP call to the worker's `onRequest`. */
|
|
62
|
+
async roomRequest(docName, init) {
|
|
63
|
+
const pk = this.partyConfig();
|
|
64
|
+
const controller = new AbortController();
|
|
65
|
+
const timeout = setTimeout(() => controller.abort(), pk.fetchTimeoutMs ?? 10_000);
|
|
66
|
+
try {
|
|
67
|
+
const requestInit = {
|
|
68
|
+
method: init.method,
|
|
69
|
+
headers: init.headers ?? {},
|
|
70
|
+
signal: controller.signal,
|
|
71
|
+
};
|
|
72
|
+
if (init.body) {
|
|
73
|
+
requestInit.body = init.body;
|
|
74
|
+
}
|
|
75
|
+
return await fetch(this.roomUrl(docName), requestInit);
|
|
76
|
+
}
|
|
77
|
+
finally {
|
|
78
|
+
clearTimeout(timeout);
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
/**
|
|
82
|
+
* Intentional no-op: the WebSocket lives in the PartyKit worker, not the
|
|
83
|
+
* Node server. Kept on the interface for provider lifecycle compatibility
|
|
84
|
+
* (which calls attach during ready()).
|
|
85
|
+
*/
|
|
86
|
+
async attach(_server) {
|
|
87
|
+
void _server;
|
|
88
|
+
}
|
|
89
|
+
async getDocumentState(docName) {
|
|
90
|
+
const response = await this.roomRequest(docName, {
|
|
91
|
+
method: 'GET',
|
|
92
|
+
headers: { 'x-collab-return': 'update' },
|
|
93
|
+
});
|
|
94
|
+
if (!response.ok) {
|
|
95
|
+
throw new PartyKitRoomError(response.status, await response.text(), docName);
|
|
96
|
+
}
|
|
97
|
+
return new Uint8Array(await response.arrayBuffer());
|
|
98
|
+
}
|
|
99
|
+
async getDocumentText(docName) {
|
|
100
|
+
const response = await this.roomRequest(docName, {
|
|
101
|
+
method: 'GET',
|
|
102
|
+
headers: { 'x-collab-return': 'text' },
|
|
103
|
+
});
|
|
104
|
+
if (!response.ok) {
|
|
105
|
+
throw new PartyKitRoomError(response.status, await response.text(), docName);
|
|
106
|
+
}
|
|
107
|
+
return response.text();
|
|
108
|
+
}
|
|
109
|
+
async createVersion(docName, createdBy, label) {
|
|
110
|
+
// Snapshots the current edge room state into the local storage.
|
|
111
|
+
const state = await this.getDocumentState(docName);
|
|
112
|
+
const existing = await this.storage.listVersions(docName);
|
|
113
|
+
const version = createVersionMetadata(createdBy, label, seqVersions(existing));
|
|
114
|
+
await this.storage.saveVersion(docName, version, state);
|
|
115
|
+
return version;
|
|
116
|
+
}
|
|
117
|
+
async listVersions(docName) {
|
|
118
|
+
return this.storage.listVersions(docName);
|
|
119
|
+
}
|
|
120
|
+
async restoreVersion(docName, versionId, restoredBy) {
|
|
121
|
+
void restoredBy;
|
|
122
|
+
const snapshot = await this.storage.loadVersionSnapshot(docName, versionId);
|
|
123
|
+
if (!snapshot)
|
|
124
|
+
throw new Error(`Version ${versionId} not found`);
|
|
125
|
+
// POSTs the full snapshot: the worker applies the update to the live
|
|
126
|
+
// doc (idempotent CRDT merge) and the protocol fans out to clients.
|
|
127
|
+
const response = await this.roomRequest(docName, {
|
|
128
|
+
method: 'POST',
|
|
129
|
+
headers: { 'content-type': 'application/octet-stream' },
|
|
130
|
+
body: snapshot,
|
|
131
|
+
});
|
|
132
|
+
if (!response.ok) {
|
|
133
|
+
throw new PartyKitRoomError(response.status, await response.text(), docName);
|
|
134
|
+
}
|
|
135
|
+
await this.storage.saveDocument(docName, snapshot);
|
|
136
|
+
}
|
|
137
|
+
async diffVersions(docName, aId, bId) {
|
|
138
|
+
const [aBytes, bBytes] = await Promise.all([
|
|
139
|
+
this.storage.loadVersionSnapshot(docName, aId),
|
|
140
|
+
this.storage.loadVersionSnapshot(docName, bId),
|
|
141
|
+
]);
|
|
142
|
+
if (!aBytes || !bBytes)
|
|
143
|
+
throw new Error('Version not found');
|
|
144
|
+
const textOf = (bytes) => {
|
|
145
|
+
try {
|
|
146
|
+
const doc = new Y.Doc();
|
|
147
|
+
Y.applyUpdate(doc, bytes);
|
|
148
|
+
const json = TiptapTransformer.fromYdoc(doc);
|
|
149
|
+
const defaultDoc = json.default ?? json;
|
|
150
|
+
return tiptapJsonToText(defaultDoc);
|
|
151
|
+
}
|
|
152
|
+
catch {
|
|
153
|
+
return '';
|
|
154
|
+
}
|
|
155
|
+
};
|
|
156
|
+
return lineDiff(textOf(aBytes), textOf(bBytes));
|
|
157
|
+
}
|
|
158
|
+
async close() {
|
|
159
|
+
// No local resources: no WS server, timers or open connections.
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
//# sourceMappingURL=partykit_driver.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"partykit_driver.js","sourceRoot":"","sources":["../../../../src/drivers/partykit/partykit_driver.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import type { CollaborationStorage } from '../types.js';
|
|
2
|
+
/**
|
|
3
|
+
* Pure helpers shared by every driver. Nothing here knows about transports
|
|
4
|
+
* or engines — drivers stay connected only through the registry.
|
|
5
|
+
*/
|
|
6
|
+
/** Resolves the storage dependency, defaulting to in-memory (dev only). */
|
|
7
|
+
export declare function resolveStorage(storage?: CollaborationStorage): CollaborationStorage;
|
|
8
|
+
/** Line-based diff summary — added/removed counts between two texts. */
|
|
9
|
+
export declare function lineDiff(a: string, b: string): {
|
|
10
|
+
added: number;
|
|
11
|
+
removed: number;
|
|
12
|
+
};
|
|
13
|
+
type JsonNode = Record<string, unknown>;
|
|
14
|
+
/** Converts a Tiptap JSON document into plain text (\n\n between blocks). */
|
|
15
|
+
export declare function tiptapJsonToText(node: JsonNode | null | undefined): string;
|
|
16
|
+
export {};
|
|
17
|
+
//# sourceMappingURL=shared.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"shared.d.ts","sourceRoot":"","sources":["../../../src/drivers/shared.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,oBAAoB,EAAE,MAAM,aAAa,CAAC;AAExD;;;GAGG;AAEH,2EAA2E;AAC3E,wBAAgB,cAAc,CAAC,OAAO,CAAC,EAAE,oBAAoB,GAAG,oBAAoB,CAEnF;AAED,wEAAwE;AACxE,wBAAgB,QAAQ,CAAC,CAAC,EAAE,MAAM,EAAE,CAAC,EAAE,MAAM,GAAG;IAAE,KAAK,EAAE,MAAM,CAAC;IAAC,OAAO,EAAE,MAAM,CAAA;CAAE,CAUjF;AAED,KAAK,QAAQ,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;AAExC,6EAA6E;AAC7E,wBAAgB,gBAAgB,CAAC,IAAI,EAAE,QAAQ,GAAG,IAAI,GAAG,SAAS,GAAG,MAAM,CAqB1E"}
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
import { InMemoryCollaborationStorage } from '../storage/in_memory_storage.js';
|
|
2
|
+
/**
|
|
3
|
+
* Pure helpers shared by every driver. Nothing here knows about transports
|
|
4
|
+
* or engines — drivers stay connected only through the registry.
|
|
5
|
+
*/
|
|
6
|
+
/** Resolves the storage dependency, defaulting to in-memory (dev only). */
|
|
7
|
+
export function resolveStorage(storage) {
|
|
8
|
+
return storage ?? new InMemoryCollaborationStorage();
|
|
9
|
+
}
|
|
10
|
+
/** Line-based diff summary — added/removed counts between two texts. */
|
|
11
|
+
export function lineDiff(a, b) {
|
|
12
|
+
const linesA = new Set(a.split('\n'));
|
|
13
|
+
const linesB = new Set(b.split('\n'));
|
|
14
|
+
let added = 0;
|
|
15
|
+
for (const line of linesB)
|
|
16
|
+
if (!linesA.has(line))
|
|
17
|
+
added++;
|
|
18
|
+
let removed = 0;
|
|
19
|
+
for (const line of linesA)
|
|
20
|
+
if (!linesB.has(line))
|
|
21
|
+
removed++;
|
|
22
|
+
return { added, removed };
|
|
23
|
+
}
|
|
24
|
+
/** Converts a Tiptap JSON document into plain text (\n\n between blocks). */
|
|
25
|
+
export function tiptapJsonToText(node) {
|
|
26
|
+
if (!node || !Array.isArray(node.content))
|
|
27
|
+
return '';
|
|
28
|
+
let text = '';
|
|
29
|
+
const walk = (current) => {
|
|
30
|
+
if (current.type === 'text' && typeof current.text === 'string') {
|
|
31
|
+
text += current.text;
|
|
32
|
+
return;
|
|
33
|
+
}
|
|
34
|
+
if (Array.isArray(current.content)) {
|
|
35
|
+
for (const child of current.content)
|
|
36
|
+
walk(child);
|
|
37
|
+
}
|
|
38
|
+
if (typeof current.type === 'string' &&
|
|
39
|
+
['paragraph', 'heading', 'blockquote', 'codeBlock', 'listItem'].includes(current.type)) {
|
|
40
|
+
text += '\n\n';
|
|
41
|
+
}
|
|
42
|
+
};
|
|
43
|
+
walk(node);
|
|
44
|
+
return text.trimEnd();
|
|
45
|
+
}
|
|
46
|
+
//# sourceMappingURL=shared.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"shared.js","sourceRoot":"","sources":["../../../src/drivers/shared.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,4BAA4B,EAAE,MAAM,iCAAiC,CAAC;AAG/E;;;GAGG;AAEH,2EAA2E;AAC3E,MAAM,UAAU,cAAc,CAAC,OAA8B;IAC3D,OAAO,OAAO,IAAI,IAAI,4BAA4B,EAAE,CAAC;AACvD,CAAC;AAED,wEAAwE;AACxE,MAAM,UAAU,QAAQ,CAAC,CAAS,EAAE,CAAS;IAC3C,MAAM,MAAM,GAAG,IAAI,GAAG,CAAC,CAAC,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC,CAAC;IACtC,MAAM,MAAM,GAAG,IAAI,GAAG,CAAC,CAAC,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC,CAAC;IAEtC,IAAI,KAAK,GAAG,CAAC,CAAC;IACd,KAAK,MAAM,IAAI,IAAI,MAAM;QAAE,IAAI,CAAC,MAAM,CAAC,GAAG,CAAC,IAAI,CAAC;YAAE,KAAK,EAAE,CAAC;IAC1D,IAAI,OAAO,GAAG,CAAC,CAAC;IAChB,KAAK,MAAM,IAAI,IAAI,MAAM;QAAE,IAAI,CAAC,MAAM,CAAC,GAAG,CAAC,IAAI,CAAC;YAAE,OAAO,EAAE,CAAC;IAE5D,OAAO,EAAE,KAAK,EAAE,OAAO,EAAE,CAAC;AAC5B,CAAC;AAID,6EAA6E;AAC7E,MAAM,UAAU,gBAAgB,CAAC,IAAiC;IAChE,IAAI,CAAC,IAAI,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,IAAI,CAAC,OAAO,CAAC;QAAE,OAAO,EAAE,CAAC;IAErD,IAAI,IAAI,GAAG,EAAE,CAAC;IACd,MAAM,IAAI,GAAG,CAAC,OAAiB,EAAE,EAAE;QACjC,IAAI,OAAO,CAAC,IAAI,KAAK,MAAM,IAAI,OAAO,OAAO,CAAC,IAAI,KAAK,QAAQ,EAAE,CAAC;YAChE,IAAI,IAAI,OAAO,CAAC,IAAI,CAAC;YACrB,OAAO;QACT,CAAC;QACD,IAAI,KAAK,CAAC,OAAO,CAAC,OAAO,CAAC,OAAO,CAAC,EAAE,CAAC;YACnC,KAAK,MAAM,KAAK,IAAI,OAAO,CAAC,OAAqB;gBAAE,IAAI,CAAC,KAAK,CAAC,CAAC;QACjE,CAAC;QACD,IACE,OAAO,OAAO,CAAC,IAAI,KAAK,QAAQ;YAChC,CAAC,WAAW,EAAE,SAAS,EAAE,YAAY,EAAE,WAAW,EAAE,UAAU,CAAC,CAAC,QAAQ,CAAC,OAAO,CAAC,IAAI,CAAC,EACtF,CAAC;YACD,IAAI,IAAI,MAAM,CAAC;QACjB,CAAC;IACH,CAAC,CAAC;IACF,IAAI,CAAC,IAAI,CAAC,CAAC;IACX,OAAO,IAAI,CAAC,OAAO,EAAE,CAAC;AACxB,CAAC"}
|
|
@@ -1,13 +1,25 @@
|
|
|
1
|
-
import type { CollaborationDriver } from '../../driver.js';
|
|
2
|
-
import type {
|
|
1
|
+
import type { CollaborationDriver, SelfHostedDriverOptions } from '../../driver.js';
|
|
2
|
+
import type { CollabDiffSummary, CollabVersion } from '../../types.js';
|
|
3
|
+
/**
|
|
4
|
+
* Yjs driver: Hocuspocus as the sync engine (y-protocols protocol over
|
|
5
|
+
* WebSocket), with version control implemented on top of complete binary
|
|
6
|
+
* updates — what Automerge has natively (history) we provide here:
|
|
7
|
+
*
|
|
8
|
+
* - `createVersion`: stores the complete binary update + metadata;
|
|
9
|
+
* - `restoreVersion`: applies the version's content to the live doc via a
|
|
10
|
+
* transaction — connected clients receive it in real time through the sync
|
|
11
|
+
* protocol;
|
|
12
|
+
* - `diffVersions`: materializes both versions and compares them by line.
|
|
13
|
+
* - Anchored comments: delegate to the CommentService (generic anchor).
|
|
14
|
+
*/
|
|
15
|
+
/** Permissions per connection (userId:docName), filled in at authenticate. */
|
|
3
16
|
export declare class YjsDriver implements CollaborationDriver {
|
|
4
|
-
#private;
|
|
5
17
|
readonly engine = "yjs";
|
|
6
18
|
private hocuspocus;
|
|
7
19
|
private wss?;
|
|
8
|
-
private
|
|
9
|
-
private
|
|
10
|
-
constructor(
|
|
20
|
+
private readonly options;
|
|
21
|
+
private readonly storage;
|
|
22
|
+
constructor(options: SelfHostedDriverOptions);
|
|
11
23
|
private liveDoc;
|
|
12
24
|
private hydrate;
|
|
13
25
|
private ensureDoc;
|
|
@@ -19,12 +31,6 @@ export declare class YjsDriver implements CollaborationDriver {
|
|
|
19
31
|
restoreVersion(docName: string, versionId: string, restoredBy: string | null): Promise<void>;
|
|
20
32
|
diffVersions(docName: string, aId: string, bId: string): Promise<CollabDiffSummary>;
|
|
21
33
|
private versionState;
|
|
22
|
-
listComments(docName: string, space?: string): Promise<CollabComment[]>;
|
|
23
|
-
createComment(docName: string, comment: Omit<CollabComment, 'id' | 'createdAt' | 'updatedAt' | 'resolvedAt'>): Promise<CollabComment>;
|
|
24
|
-
resolveComment(docName: string, commentId: string, resolved: boolean): Promise<CollabComment | null>;
|
|
25
|
-
deleteComment(docName: string, commentId: string): Promise<boolean>;
|
|
26
34
|
close(): Promise<void>;
|
|
27
35
|
}
|
|
28
|
-
/** Converte o JSON Tiptap em texto plano (\n\n entre blocos). */
|
|
29
|
-
export declare function tiptapJsonToText(node: Record<string, unknown> | null): string;
|
|
30
36
|
//# sourceMappingURL=yjs_driver.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"yjs_driver.d.ts","sourceRoot":"","sources":["../../../../src/drivers/yjs/yjs_driver.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"yjs_driver.d.ts","sourceRoot":"","sources":["../../../../src/drivers/yjs/yjs_driver.ts"],"names":[],"mappings":"AAKA,OAAO,KAAK,EAAE,mBAAmB,EAAE,uBAAuB,EAAE,MAAM,iBAAiB,CAAC;AACpF,OAAO,KAAK,EAAE,iBAAiB,EAAE,aAAa,EAAwB,MAAM,gBAAgB,CAAC;AAI7F;;;;;;;;;;;GAWG;AAEH,8EAA8E;AAE9E,qBAAa,SAAU,YAAW,mBAAmB;IACnD,QAAQ,CAAC,MAAM,SAAS;IAExB,OAAO,CAAC,UAAU,CAAa;IAC/B,OAAO,CAAC,GAAG,CAAC,CAAkB;IAC9B,OAAO,CAAC,QAAQ,CAAC,OAAO,CAA0B;IAClD,OAAO,CAAC,QAAQ,CAAC,OAAO,CAAuB;gBAEnC,OAAO,EAAE,uBAAuB;IA0B5C,OAAO,CAAC,OAAO;IAKf,OAAO,CAAC,OAAO;YAQD,SAAS;IAQjB,MAAM,CAAC,MAAM,EAAE,OAAO,WAAW,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAoBzD,gBAAgB,CAAC,OAAO,EAAE,MAAM,GAAG,OAAO,CAAC,UAAU,CAAC;IAStD,eAAe,CAAC,OAAO,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC;IAajD,aAAa,CACjB,OAAO,EAAE,MAAM,EACf,SAAS,EAAE,MAAM,GAAG,IAAI,EACxB,KAAK,EAAE,MAAM,GAAG,IAAI,GACnB,OAAO,CAAC,aAAa,CAAC;IASnB,YAAY,CAAC,OAAO,EAAE,MAAM,GAAG,OAAO,CAAC,aAAa,EAAE,CAAC;IAIvD,cAAc,CAClB,OAAO,EAAE,MAAM,EACf,SAAS,EAAE,MAAM,EACjB,UAAU,EAAE,MAAM,GAAG,IAAI,GACxB,OAAO,CAAC,IAAI,CAAC;IAgBV,YAAY,CAAC,OAAO,EAAE,MAAM,EAAE,GAAG,EAAE,MAAM,EAAE,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,iBAAiB,CAAC;YAY3E,YAAY;IAMpB,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC;CAU7B"}
|
|
@@ -2,60 +2,52 @@ import { Hocuspocus } from '@hocuspocus/server';
|
|
|
2
2
|
import { TiptapTransformer } from '@hocuspocus/transformer';
|
|
3
3
|
import { WebSocketServer } from 'ws';
|
|
4
4
|
import * as Y from 'yjs';
|
|
5
|
-
import {
|
|
6
|
-
import { InMemoryCollaborationStorage } from '../../storage/in_memory_storage.js';
|
|
5
|
+
import { parseLegacyToken } from '../../auth/token.js';
|
|
7
6
|
import { createVersionMetadata, seqVersions } from '../../versioning.js';
|
|
7
|
+
import { lineDiff, resolveStorage, tiptapJsonToText } from '../shared.js';
|
|
8
8
|
/**
|
|
9
|
-
*
|
|
10
|
-
* WebSocket),
|
|
11
|
-
*
|
|
9
|
+
* Yjs driver: Hocuspocus as the sync engine (y-protocols protocol over
|
|
10
|
+
* WebSocket), with version control implemented on top of complete binary
|
|
11
|
+
* updates — what Automerge has natively (history) we provide here:
|
|
12
12
|
*
|
|
13
|
-
* - `createVersion`:
|
|
14
|
-
* - `restoreVersion`:
|
|
15
|
-
* —
|
|
16
|
-
*
|
|
17
|
-
* -
|
|
13
|
+
* - `createVersion`: stores the complete binary update + metadata;
|
|
14
|
+
* - `restoreVersion`: applies the version's content to the live doc via a
|
|
15
|
+
* transaction — connected clients receive it in real time through the sync
|
|
16
|
+
* protocol;
|
|
17
|
+
* - `diffVersions`: materializes both versions and compares them by line.
|
|
18
|
+
* - Anchored comments: delegate to the CommentService (generic anchor).
|
|
18
19
|
*/
|
|
19
|
-
/**
|
|
20
|
-
const permissions = new Map();
|
|
20
|
+
/** Permissions per connection (userId:docName), filled in at authenticate. */
|
|
21
21
|
export class YjsDriver {
|
|
22
22
|
engine = 'yjs';
|
|
23
23
|
hocuspocus;
|
|
24
24
|
wss;
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
this.#memoryStorage ??= new InMemoryCollaborationStorage();
|
|
34
|
-
return this.#memoryStorage;
|
|
35
|
-
}
|
|
36
|
-
constructor(config) {
|
|
37
|
-
this.config = config;
|
|
38
|
-
this.commentsService = new CommentService(config.storage ?? new InMemoryCollaborationStorage());
|
|
39
|
-
// Capturados fora do callback: o `this` dentro dos hooks do Hocuspocus
|
|
40
|
-
// não é a instância do driver.
|
|
41
|
-
const storage = () => this.#storage();
|
|
25
|
+
options;
|
|
26
|
+
storage;
|
|
27
|
+
constructor(options) {
|
|
28
|
+
this.options = options;
|
|
29
|
+
this.storage = resolveStorage(options.storage);
|
|
30
|
+
// Captured outside the callback: `this` inside Hocuspocus hooks is not
|
|
31
|
+
// the driver instance.
|
|
32
|
+
const storage = this.storage;
|
|
42
33
|
this.hocuspocus = new Hocuspocus({
|
|
43
34
|
async onAuthenticate({ documentName, token }) {
|
|
44
|
-
const ctx =
|
|
45
|
-
|
|
35
|
+
const ctx = parseLegacyToken(token);
|
|
36
|
+
if (!ctx)
|
|
37
|
+
throw new Error('invalid token');
|
|
38
|
+
const permission = await options.authorize(ctx, documentName);
|
|
46
39
|
if (!permission.canRead) {
|
|
47
|
-
throw new Error('
|
|
40
|
+
throw new Error('no access to this document');
|
|
48
41
|
}
|
|
49
|
-
permissions.set(`${ctx.userId}:${documentName}`, permission);
|
|
50
42
|
},
|
|
51
43
|
async onStoreDocument({ documentName, document }) {
|
|
52
|
-
await storage
|
|
44
|
+
await storage.saveDocument(documentName, Y.encodeStateAsUpdate(document));
|
|
53
45
|
},
|
|
54
|
-
debounce:
|
|
46
|
+
debounce: options.debounce ?? 2000,
|
|
55
47
|
});
|
|
56
48
|
}
|
|
57
49
|
liveDoc(name) {
|
|
58
|
-
//
|
|
50
|
+
// The Hocuspocus document extends Y.Doc directly.
|
|
59
51
|
return this.hocuspocus.documents.get(name);
|
|
60
52
|
}
|
|
61
53
|
hydrate(state) {
|
|
@@ -69,11 +61,11 @@ export class YjsDriver {
|
|
|
69
61
|
const existing = this.liveDoc(name);
|
|
70
62
|
if (existing)
|
|
71
63
|
return existing;
|
|
72
|
-
const stored = await this
|
|
64
|
+
const stored = await this.storage.loadDocument(name);
|
|
73
65
|
return this.hydrate(stored?.state ?? new Uint8Array());
|
|
74
66
|
}
|
|
75
67
|
async attach(server) {
|
|
76
|
-
const path = this.
|
|
68
|
+
const path = this.options.path ?? '/collaboration';
|
|
77
69
|
const wss = new WebSocketServer({ noServer: true });
|
|
78
70
|
this.wss = wss;
|
|
79
71
|
server.on('upgrade', (request, socket, head) => {
|
|
@@ -81,8 +73,8 @@ export class YjsDriver {
|
|
|
81
73
|
if (parsed.pathname !== path)
|
|
82
74
|
return;
|
|
83
75
|
wss.handleUpgrade(request, socket, head, (ws) => {
|
|
84
|
-
//
|
|
85
|
-
// IncomingMessage
|
|
76
|
+
// Hocuspocus expects a Fetch API Request; we convert the node
|
|
77
|
+
// IncomingMessage into a minimal Request with the real URL.
|
|
86
78
|
const fetchRequest = new Request(parsed, {
|
|
87
79
|
headers: request.headers,
|
|
88
80
|
});
|
|
@@ -95,7 +87,7 @@ export class YjsDriver {
|
|
|
95
87
|
if (live) {
|
|
96
88
|
return Y.encodeStateAsUpdate(live);
|
|
97
89
|
}
|
|
98
|
-
const stored = await this
|
|
90
|
+
const stored = await this.storage.loadDocument(docName);
|
|
99
91
|
return stored?.state ?? new Uint8Array();
|
|
100
92
|
}
|
|
101
93
|
async getDocumentText(docName) {
|
|
@@ -106,31 +98,31 @@ export class YjsDriver {
|
|
|
106
98
|
return tiptapJsonToText(defaultDoc);
|
|
107
99
|
}
|
|
108
100
|
catch {
|
|
109
|
-
//
|
|
101
|
+
// Empty doc or not in the Tiptap layout.
|
|
110
102
|
return '';
|
|
111
103
|
}
|
|
112
104
|
}
|
|
113
105
|
async createVersion(docName, createdBy, label) {
|
|
114
106
|
const doc = await this.ensureDoc(docName);
|
|
115
|
-
const existing = await this
|
|
107
|
+
const existing = await this.storage.listVersions(docName);
|
|
116
108
|
const version = createVersionMetadata(createdBy, label, seqVersions(existing));
|
|
117
|
-
await this
|
|
109
|
+
await this.storage.saveVersion(docName, version, Y.encodeStateAsUpdate(doc));
|
|
118
110
|
return version;
|
|
119
111
|
}
|
|
120
112
|
async listVersions(docName) {
|
|
121
|
-
return this
|
|
113
|
+
return this.storage.listVersions(docName);
|
|
122
114
|
}
|
|
123
115
|
async restoreVersion(docName, versionId, restoredBy) {
|
|
124
116
|
void restoredBy;
|
|
125
117
|
const state = await this.versionState(docName, versionId);
|
|
126
118
|
const restoredDoc = this.hydrate(state);
|
|
127
|
-
//
|
|
128
|
-
//
|
|
119
|
+
// Applies the restored content to the live doc — connected clients receive
|
|
120
|
+
// the change in real time via the Hocuspocus sync protocol.
|
|
129
121
|
const live = await this.ensureDoc(docName);
|
|
130
122
|
live.transact(() => {
|
|
131
123
|
applyRestoredContent(live, restoredDoc);
|
|
132
124
|
});
|
|
133
|
-
await this
|
|
125
|
+
await this.storage.saveDocument(docName, Y.encodeStateAsUpdate(live));
|
|
134
126
|
}
|
|
135
127
|
async diffVersions(docName, aId, bId) {
|
|
136
128
|
const [aState, bState] = await Promise.all([
|
|
@@ -139,30 +131,17 @@ export class YjsDriver {
|
|
|
139
131
|
]);
|
|
140
132
|
const textA = tiptapJsonToText(safeTiptapFromYdoc(this.hydrate(aState)));
|
|
141
133
|
const textB = tiptapJsonToText(safeTiptapFromYdoc(this.hydrate(bState)));
|
|
142
|
-
return
|
|
134
|
+
return lineDiff(textA, textB);
|
|
143
135
|
}
|
|
144
136
|
async versionState(docName, versionId) {
|
|
145
|
-
const persisted = await this
|
|
137
|
+
const persisted = await this.storage.loadVersionSnapshot(docName, versionId);
|
|
146
138
|
if (!persisted)
|
|
147
|
-
throw new Error(`
|
|
139
|
+
throw new Error(`Version ${versionId} not found`);
|
|
148
140
|
return persisted;
|
|
149
141
|
}
|
|
150
|
-
/* ───────────────────────── comentários ancorados ───────────────────────── */
|
|
151
|
-
async listComments(docName, space) {
|
|
152
|
-
return this.commentsService.list(docName, space);
|
|
153
|
-
}
|
|
154
|
-
async createComment(docName, comment) {
|
|
155
|
-
return this.commentsService.create(docName, comment);
|
|
156
|
-
}
|
|
157
|
-
async resolveComment(docName, commentId, resolved) {
|
|
158
|
-
return this.commentsService.resolve(docName, commentId, resolved);
|
|
159
|
-
}
|
|
160
|
-
async deleteComment(docName, commentId) {
|
|
161
|
-
return this.commentsService.remove(docName, commentId);
|
|
162
|
-
}
|
|
163
142
|
async close() {
|
|
164
|
-
//
|
|
165
|
-
// destroy —
|
|
143
|
+
// Persists pending docs and closes connections (the Hocuspocus class has
|
|
144
|
+
// no destroy — the embedded Server does).
|
|
166
145
|
this.hocuspocus.flushPendingStores();
|
|
167
146
|
this.hocuspocus.closeConnections();
|
|
168
147
|
if (!this.wss)
|
|
@@ -172,7 +151,7 @@ export class YjsDriver {
|
|
|
172
151
|
});
|
|
173
152
|
}
|
|
174
153
|
}
|
|
175
|
-
/* ───────────────────────── helpers
|
|
154
|
+
/* ───────────────────────── local helpers ───────────────────────── */
|
|
176
155
|
function safeTiptapFromYdoc(doc) {
|
|
177
156
|
try {
|
|
178
157
|
return TiptapTransformer.fromYdoc(doc);
|
|
@@ -181,30 +160,7 @@ function safeTiptapFromYdoc(doc) {
|
|
|
181
160
|
return {};
|
|
182
161
|
}
|
|
183
162
|
}
|
|
184
|
-
/**
|
|
185
|
-
export function tiptapJsonToText(node) {
|
|
186
|
-
if (!node || !Array.isArray(node.content))
|
|
187
|
-
return '';
|
|
188
|
-
let text = '';
|
|
189
|
-
const walk = (current) => {
|
|
190
|
-
if (current.type === 'text' && typeof current.text === 'string') {
|
|
191
|
-
text += current.text;
|
|
192
|
-
return;
|
|
193
|
-
}
|
|
194
|
-
if (Array.isArray(current.content)) {
|
|
195
|
-
for (const child of current.content) {
|
|
196
|
-
walk(child);
|
|
197
|
-
}
|
|
198
|
-
}
|
|
199
|
-
if (typeof current.type === 'string' &&
|
|
200
|
-
['paragraph', 'heading', 'blockquote', 'codeBlock', 'listItem'].includes(current.type)) {
|
|
201
|
-
text += '\n\n';
|
|
202
|
-
}
|
|
203
|
-
};
|
|
204
|
-
walk(node);
|
|
205
|
-
return text.trimEnd();
|
|
206
|
-
}
|
|
207
|
-
/** Diff por linha simples o suficiente pra resumo (+adicionadas / -removidas). */
|
|
163
|
+
/** Per-line diff, simple enough for the summary (+added / -removed). */
|
|
208
164
|
function simpleLineDiff(a, b) {
|
|
209
165
|
const linesA = new Set(a.split('\n'));
|
|
210
166
|
const linesB = new Set(b.split('\n'));
|
|
@@ -221,9 +177,9 @@ function simpleLineDiff(a, b) {
|
|
|
221
177
|
return { added, removed };
|
|
222
178
|
}
|
|
223
179
|
/**
|
|
224
|
-
*
|
|
225
|
-
*
|
|
226
|
-
*
|
|
180
|
+
* Replaces the live doc's content by applying the restored doc's full update
|
|
181
|
+
* (CRDT merge is idempotent; the preceding delete removes the divergent state
|
|
182
|
+
* and the update brings the old content back).
|
|
227
183
|
*/
|
|
228
184
|
function applyRestoredContent(live, restored) {
|
|
229
185
|
const liveFragment = live.getXmlFragment('default');
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"yjs_driver.js","sourceRoot":"","sources":["../../../../src/drivers/yjs/yjs_driver.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAA8B,MAAM,oBAAoB,CAAC;AAC5E,OAAO,EAAE,iBAAiB,EAAE,MAAM,yBAAyB,CAAC;AAC5D,OAAO,EAAE,eAAe,EAAE,MAAM,IAAI,CAAC;AACrC,OAAO,KAAK,CAAC,MAAM,KAAK,CAAC;
|
|
1
|
+
{"version":3,"file":"yjs_driver.js","sourceRoot":"","sources":["../../../../src/drivers/yjs/yjs_driver.ts"],"names":[],"mappings":"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"}
|