@skaile/connector-yjs 0.1.0 → 0.1.2
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 +22 -0
- package/dist/index.d.ts +7 -0
- package/dist/index.js +76 -55
- package/dist/index.js.map +1 -1
- package/package.json +2 -2
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,27 @@
|
|
|
1
1
|
# @skaile/connector-yjs
|
|
2
2
|
|
|
3
|
+
## 0.1.2
|
|
4
|
+
|
|
5
|
+
### Patch Changes
|
|
6
|
+
|
|
7
|
+
- [#115](https://github.com/skaile-ai/workspaces/pull/115) [`ed78a7d`](https://github.com/skaile-ai/workspaces/commit/ed78a7d1f9e9de7cca8be861c6198b600aa85ba1) Thanks [@mortegro](https://github.com/mortegro)! - Internal code-quality cleanup guided by `fallow`: removed dead code (3 orphan dev
|
|
8
|
+
scripts, an unused integration test setup, ~15 internal-only exports/re-exports, 2
|
|
9
|
+
unused private methods, and the unused `picomatch` dependency) and reduced cognitive
|
|
10
|
+
complexity across 48 source files via behavior-preserving extract-function refactors
|
|
11
|
+
(e.g. `serve.ts` `handleCommand` 336→thin dispatcher; `workspace-config.ts`,
|
|
12
|
+
`publish-manifest.ts`, `claude-sdk.ts`, and many others). No public API, exported
|
|
13
|
+
signatures, or runtime behavior changed; the full test suite (3121 tests) passes
|
|
14
|
+
unchanged.
|
|
15
|
+
|
|
16
|
+
## 0.1.1
|
|
17
|
+
|
|
18
|
+
### Patch Changes
|
|
19
|
+
|
|
20
|
+
- [#99](https://github.com/skaile-ai/workspaces/pull/99) [`fb76100`](https://github.com/skaile-ai/workspaces/commit/fb76100d1524784ccfafb52520280e1764e0c38a) Thanks [@peteralbert](https://github.com/peteralbert)! - Fix change-event classification in the Yjs target. `merge` now snapshots key
|
|
21
|
+
existence before the transaction, so newly created keys emit `action: "create"`
|
|
22
|
+
instead of always `"edit"`. The tool-face `_write` path now emits a change event
|
|
23
|
+
(with the correct `create`/`edit` action) consistent with `set`/`del`.
|
|
24
|
+
|
|
3
25
|
## 0.1.0
|
|
4
26
|
|
|
5
27
|
### Minor Changes
|
package/dist/index.d.ts
CHANGED
|
@@ -35,6 +35,13 @@ declare class YjsConnector extends AbstractConnector {
|
|
|
35
35
|
private _search;
|
|
36
36
|
private _describeOperations;
|
|
37
37
|
private _executeOp;
|
|
38
|
+
private _opSnapshot;
|
|
39
|
+
private _opGet;
|
|
40
|
+
private _opKeys;
|
|
41
|
+
private _opHistory;
|
|
42
|
+
private _opSet;
|
|
43
|
+
private _opMerge;
|
|
44
|
+
private _opDel;
|
|
38
45
|
private _getY;
|
|
39
46
|
/** Convert a JS value to a Yjs-compatible value. */
|
|
40
47
|
private _toYjsValue;
|
package/dist/index.js
CHANGED
|
@@ -87,10 +87,12 @@ var YjsConnector = class extends AbstractConnector {
|
|
|
87
87
|
}
|
|
88
88
|
const Y = await this._getY();
|
|
89
89
|
const root = this.s(handle).root;
|
|
90
|
+
const hadKey = root.has(path);
|
|
90
91
|
this.s(handle).doc.transact(() => {
|
|
91
92
|
root.set(path, this._toYjsValue(Y, value));
|
|
92
93
|
});
|
|
93
94
|
this._recordHistory(handle, "set", path);
|
|
95
|
+
this.emitChange?.({ path, action: hadKey ? "edit" : "create", source: "operation" });
|
|
94
96
|
}
|
|
95
97
|
async _delete(handle, path) {
|
|
96
98
|
const root = this.s(handle).root;
|
|
@@ -191,66 +193,85 @@ var YjsConnector = class extends AbstractConnector {
|
|
|
191
193
|
const Y = await this._getY();
|
|
192
194
|
switch (operation) {
|
|
193
195
|
case "snapshot":
|
|
194
|
-
return
|
|
195
|
-
case "get":
|
|
196
|
-
|
|
197
|
-
const value = s.root.get(key);
|
|
198
|
-
return value !== void 0 ? JSON.stringify(this._fromYjsValue(value)) : "";
|
|
199
|
-
}
|
|
196
|
+
return this._opSnapshot(s);
|
|
197
|
+
case "get":
|
|
198
|
+
return this._opGet(s, args);
|
|
200
199
|
case "keys":
|
|
201
|
-
return
|
|
202
|
-
case "history":
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
case "
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
try {
|
|
211
|
-
value = JSON.parse(value);
|
|
212
|
-
} catch {
|
|
213
|
-
}
|
|
214
|
-
}
|
|
215
|
-
const hadKey = s.root.has(key);
|
|
216
|
-
s.doc.transact(() => {
|
|
217
|
-
s.root.set(key, this._toYjsValue(Y, value));
|
|
218
|
-
});
|
|
219
|
-
this._recordHistory(handle, "set", key);
|
|
220
|
-
this.emitChange?.({ path: key, action: hadKey ? "edit" : "create", source: "operation" });
|
|
221
|
-
return "OK";
|
|
222
|
-
}
|
|
223
|
-
case "merge": {
|
|
224
|
-
let data = args.data;
|
|
225
|
-
if (typeof data === "string") data = JSON.parse(data);
|
|
226
|
-
if (typeof data !== "object" || data === null) return "Error: data must be a JSON object";
|
|
227
|
-
s.doc.transact(() => {
|
|
228
|
-
for (const [key, value] of Object.entries(data)) {
|
|
229
|
-
s.root.set(key, this._toYjsValue(Y, value));
|
|
230
|
-
}
|
|
231
|
-
});
|
|
232
|
-
for (const key of Object.keys(data)) {
|
|
233
|
-
this._recordHistory(handle, "set", key);
|
|
234
|
-
this.emitChange?.({
|
|
235
|
-
path: key,
|
|
236
|
-
action: s.root.has(key) ? "edit" : "create",
|
|
237
|
-
source: "operation"
|
|
238
|
-
});
|
|
239
|
-
}
|
|
240
|
-
return "OK";
|
|
241
|
-
}
|
|
242
|
-
case "del": {
|
|
243
|
-
const key = String(args.key ?? "");
|
|
244
|
-
if (!s.root.has(key)) return "Not found";
|
|
245
|
-
s.root.delete(key);
|
|
246
|
-
this._recordHistory(handle, "delete", key);
|
|
247
|
-
this.emitChange?.({ path: key, action: "delete", source: "operation" });
|
|
248
|
-
return "Deleted";
|
|
249
|
-
}
|
|
200
|
+
return this._opKeys(s);
|
|
201
|
+
case "history":
|
|
202
|
+
return this._opHistory(s, args);
|
|
203
|
+
case "set":
|
|
204
|
+
return this._opSet(handle, s, Y, args);
|
|
205
|
+
case "merge":
|
|
206
|
+
return this._opMerge(handle, s, Y, args);
|
|
207
|
+
case "del":
|
|
208
|
+
return this._opDel(handle, s, args);
|
|
250
209
|
default:
|
|
251
210
|
throw new Error(`YjsConnector has no operation: ${operation}`);
|
|
252
211
|
}
|
|
253
212
|
}
|
|
213
|
+
_opSnapshot(s) {
|
|
214
|
+
return JSON.stringify(s.root.toJSON(), null, 2);
|
|
215
|
+
}
|
|
216
|
+
_opGet(s, args) {
|
|
217
|
+
const key = String(args.key ?? "");
|
|
218
|
+
const value = s.root.get(key);
|
|
219
|
+
return value !== void 0 ? JSON.stringify(this._fromYjsValue(value)) : "";
|
|
220
|
+
}
|
|
221
|
+
_opKeys(s) {
|
|
222
|
+
return JSON.stringify([...s.root.keys()]);
|
|
223
|
+
}
|
|
224
|
+
_opHistory(s, args) {
|
|
225
|
+
const limit = Number(args.limit ?? 50);
|
|
226
|
+
return JSON.stringify(s.history.slice(-limit), null, 2);
|
|
227
|
+
}
|
|
228
|
+
_opSet(handle, s, Y, args) {
|
|
229
|
+
const key = String(args.key ?? "");
|
|
230
|
+
let value = args.value;
|
|
231
|
+
if (typeof value === "string") {
|
|
232
|
+
try {
|
|
233
|
+
value = JSON.parse(value);
|
|
234
|
+
} catch {
|
|
235
|
+
}
|
|
236
|
+
}
|
|
237
|
+
const hadKey = s.root.has(key);
|
|
238
|
+
s.doc.transact(() => {
|
|
239
|
+
s.root.set(key, this._toYjsValue(Y, value));
|
|
240
|
+
});
|
|
241
|
+
this._recordHistory(handle, "set", key);
|
|
242
|
+
this.emitChange?.({ path: key, action: hadKey ? "edit" : "create", source: "operation" });
|
|
243
|
+
return "OK";
|
|
244
|
+
}
|
|
245
|
+
_opMerge(handle, s, Y, args) {
|
|
246
|
+
let data = args.data;
|
|
247
|
+
if (typeof data === "string") data = JSON.parse(data);
|
|
248
|
+
if (typeof data !== "object" || data === null) return "Error: data must be a JSON object";
|
|
249
|
+
const existed = new Set(
|
|
250
|
+
Object.keys(data).filter((k) => s.root.has(k))
|
|
251
|
+
);
|
|
252
|
+
s.doc.transact(() => {
|
|
253
|
+
for (const [key, value] of Object.entries(data)) {
|
|
254
|
+
s.root.set(key, this._toYjsValue(Y, value));
|
|
255
|
+
}
|
|
256
|
+
});
|
|
257
|
+
for (const key of Object.keys(data)) {
|
|
258
|
+
this._recordHistory(handle, "set", key);
|
|
259
|
+
this.emitChange?.({
|
|
260
|
+
path: key,
|
|
261
|
+
action: existed.has(key) ? "edit" : "create",
|
|
262
|
+
source: "operation"
|
|
263
|
+
});
|
|
264
|
+
}
|
|
265
|
+
return "OK";
|
|
266
|
+
}
|
|
267
|
+
_opDel(handle, s, args) {
|
|
268
|
+
const key = String(args.key ?? "");
|
|
269
|
+
if (!s.root.has(key)) return "Not found";
|
|
270
|
+
s.root.delete(key);
|
|
271
|
+
this._recordHistory(handle, "delete", key);
|
|
272
|
+
this.emitChange?.({ path: key, action: "delete", source: "operation" });
|
|
273
|
+
return "Deleted";
|
|
274
|
+
}
|
|
254
275
|
// ── Private helpers ────────────────────────────────────────────────────────
|
|
255
276
|
async _getY() {
|
|
256
277
|
const yjsPkg = "yjs";
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/target.ts","../src/index.ts"],"sourcesContent":["/**\n * YjsConnector — CRDT-based shared document state via Yjs.\n * Not mountable. Provides real-time collaborative state that merges\n * concurrent edits from multiple agents and UI clients.\n *\n * Requires `yjs` (a runtime dependency of this package).\n */\n\nimport { AbstractConnector } from \"@skaile/workspaces/connectors\";\nimport type {\n ConnectContext,\n ConnectorDeclaration,\n ConnectorHandle,\n ToolFace,\n} from \"@skaile/workspaces/connectors\";\nimport type {\n ConnectorContent,\n ConnectorEntry,\n ListOptions,\n OperationDescriptor,\n SearchOptions,\n SearchResult,\n} from \"@skaile/workspaces/connectors\";\nimport type { ConnectorTarget } from \"@skaile/workspaces/plugin-registry\";\nimport * as z from \"zod\";\n\ninterface YjsState {\n doc: any; // Y.Doc\n root: any; // Y.Map (root map)\n history: Array<{ op: string; key: string; at: string }>;\n onChange?: (snapshot: Record<string, unknown>, changed: string[]) => void;\n}\n\n/**\n * Connector for Yjs CRDT-based shared document state — enables real-time collaborative state that merges concurrent edits from multiple agents and UI clients.\n * Compatible with Hocuspocus and y-websocket provider connections via `getDoc()`. Requires the `yjs` package.\n */\nexport class YjsConnector extends AbstractConnector {\n readonly name = \"yjs\";\n\n tools: ToolFace;\n\n constructor() {\n super();\n this.tools = {\n read: (handle, path) => this._read(handle, path),\n write: (handle, path, content) => this._write(handle, path, content),\n delete: (handle, path) => this._delete(handle, path),\n list: (handle, path, options) => this._list(handle, path, options),\n search: (handle, query, options) => this._search(handle, query, options),\n describeOperations: (handle) => this._describeOperations(handle),\n executeOp: (handle, operation, args) => this._executeOp(handle, operation, args),\n };\n }\n\n async connect(declaration: ConnectorDeclaration, _ctx: ConnectContext): Promise<ConnectorHandle> {\n this.log.info(\"connect\", { id: declaration.id });\n const yjsPkg = \"yjs\";\n const Y = (await import(/* @vite-ignore */ yjsPkg)) as any;\n\n const doc = new Y.Doc();\n const root = doc.getMap(\"root\");\n\n // Apply initial values\n const initial = declaration.options?.initial as Record<string, unknown> | undefined;\n if (initial) {\n doc.transact(() => {\n for (const [key, value] of Object.entries(initial)) {\n root.set(key, this._toYjsValue(Y, value));\n }\n });\n }\n\n const state: YjsState = { doc, root, history: [] };\n\n // Observe changes\n root.observe((event: any) => {\n const changed: string[] = [];\n event.changes.keys.forEach((_change: any, key: string) => {\n changed.push(key);\n });\n if (changed.length > 0 && state.onChange) {\n state.onChange(root.toJSON(), changed);\n }\n });\n\n return this.makeHandle(declaration, state);\n }\n\n async disconnect(handle: ConnectorHandle): Promise<void> {\n this.log.info(\"disconnect\");\n const s = handle.state as YjsState;\n try {\n s.doc.destroy();\n } finally {\n handle.status = \"disconnected\";\n }\n }\n\n private s(handle: ConnectorHandle): YjsState {\n return handle.state as YjsState;\n }\n\n /**\n * Get the Y.Doc for external provider connections (Hocuspocus, y-websocket).\n */\n getDoc(handle: ConnectorHandle): unknown {\n return this.s(handle).doc;\n }\n\n /**\n * Register a callback fired on every change to the root map.\n */\n onDocChange(\n handle: ConnectorHandle,\n cb: (snapshot: Record<string, unknown>, changedKeys: string[]) => void,\n ): void {\n this.s(handle).onChange = cb;\n }\n\n // ── Tool face methods (private, wired in constructor) ─────────────────────\n\n private async _read(handle: ConnectorHandle, path: string): Promise<ConnectorContent | null> {\n const root = this.s(handle).root;\n const value = root.get(path);\n if (value === undefined) return null;\n const resolved = this._fromYjsValue(value);\n return { data: JSON.stringify(resolved), contentType: \"application/json\" };\n }\n\n private async _write(\n handle: ConnectorHandle,\n path: string,\n content: ConnectorContent,\n ): Promise<void> {\n const data = typeof content.data === \"string\" ? content.data : content.data.toString(\"utf-8\");\n let value: unknown;\n try {\n value = JSON.parse(data);\n } catch {\n value = data;\n }\n\n const Y = await this._getY();\n const root = this.s(handle).root;\n this.s(handle).doc.transact(() => {\n root.set(path, this._toYjsValue(Y, value));\n });\n this._recordHistory(handle, \"set\", path);\n }\n\n private async _delete(handle: ConnectorHandle, path: string): Promise<boolean> {\n const root = this.s(handle).root;\n if (!root.has(path)) return false;\n root.delete(path);\n this._recordHistory(handle, \"delete\", path);\n return true;\n }\n\n private async _list(\n handle: ConnectorHandle,\n _path?: string,\n options?: ListOptions,\n ): Promise<ConnectorEntry[]> {\n const root = this.s(handle).root;\n const entries: ConnectorEntry[] = [];\n for (const key of root.keys()) {\n if (options?.limit && entries.length >= options.limit) break;\n entries.push({ name: key, path: key, type: \"key\" });\n }\n return entries;\n }\n\n private async _search(\n handle: ConnectorHandle,\n query: string,\n options?: SearchOptions,\n ): Promise<SearchResult[]> {\n const root = this.s(handle).root;\n const results: SearchResult[] = [];\n for (const key of root.keys()) {\n if (options?.maxResults && results.length >= options.maxResults) break;\n const value = root.get(key);\n const str = JSON.stringify(this._fromYjsValue(value));\n if (key.includes(query) || str.includes(query)) {\n results.push({ path: key, snippet: str.slice(0, 200) });\n }\n }\n return results;\n }\n\n private _describeOperations(handle: ConnectorHandle): OperationDescriptor[] {\n const ops: OperationDescriptor[] = [\n {\n name: \"snapshot\",\n description: \"Get the full document snapshot (all keys and values).\",\n accessLevel: \"read\",\n args: [],\n },\n {\n name: \"get\",\n description: \"Get a single value by key.\",\n accessLevel: \"read\",\n args: [{ name: \"key\", type: \"string\", required: true, description: \"Key name\" }],\n },\n {\n name: \"keys\",\n description: \"List all keys in the document.\",\n accessLevel: \"read\",\n args: [],\n },\n {\n name: \"history\",\n description: \"Get the local operation history.\",\n accessLevel: \"read\",\n args: [{ name: \"limit\", type: \"number\", required: false, description: \"Max entries\" }],\n },\n ];\n\n if (handle.access === \"read-write\") {\n ops.push(\n {\n name: \"set\",\n description: \"Set a key to a value. Merges with concurrent writes via CRDT.\",\n accessLevel: \"write\",\n args: [\n { name: \"key\", type: \"string\", required: true, description: \"Key name\" },\n {\n name: \"value\",\n type: \"string\",\n required: true,\n description: \"Value (JSON-encoded for objects)\",\n },\n ],\n examples: [\n \"set --key theme --value '\\\"dark\\\"'\",\n \"set --key config --value '{\\\"debug\\\":true}'\",\n ],\n },\n {\n name: \"merge\",\n description: \"Merge an object into the document (each key set independently via CRDT).\",\n accessLevel: \"write\",\n args: [\n { name: \"data\", type: \"json\", required: true, description: \"JSON object to merge\" },\n ],\n },\n {\n name: \"del\",\n description: \"Delete a key from the document.\",\n accessLevel: \"write\",\n args: [{ name: \"key\", type: \"string\", required: true, description: \"Key to delete\" }],\n },\n );\n }\n\n return ops;\n }\n\n private async _executeOp(\n handle: ConnectorHandle,\n operation: string,\n args: Record<string, unknown>,\n ): Promise<string> {\n const s = this.s(handle);\n const Y = await this._getY();\n\n switch (operation) {\n case \"snapshot\":\n return JSON.stringify(s.root.toJSON(), null, 2);\n\n case \"get\": {\n const key = String(args.key ?? \"\");\n const value = s.root.get(key);\n return value !== undefined ? JSON.stringify(this._fromYjsValue(value)) : \"\";\n }\n\n case \"keys\":\n return JSON.stringify([...s.root.keys()]);\n\n case \"history\": {\n const limit = Number(args.limit ?? 50);\n return JSON.stringify(s.history.slice(-limit), null, 2);\n }\n\n case \"set\": {\n const key = String(args.key ?? \"\");\n let value: unknown = args.value;\n if (typeof value === \"string\") {\n try {\n value = JSON.parse(value);\n } catch {\n /* keep as string */\n }\n }\n const hadKey = s.root.has(key);\n s.doc.transact(() => {\n s.root.set(key, this._toYjsValue(Y, value));\n });\n this._recordHistory(handle, \"set\", key);\n this.emitChange?.({ path: key, action: hadKey ? \"edit\" : \"create\", source: \"operation\" });\n return \"OK\";\n }\n\n case \"merge\": {\n let data = args.data;\n if (typeof data === \"string\") data = JSON.parse(data);\n if (typeof data !== \"object\" || data === null) return \"Error: data must be a JSON object\";\n s.doc.transact(() => {\n for (const [key, value] of Object.entries(data as Record<string, unknown>)) {\n s.root.set(key, this._toYjsValue(Y, value));\n }\n });\n for (const key of Object.keys(data as Record<string, unknown>)) {\n this._recordHistory(handle, \"set\", key);\n this.emitChange?.({\n path: key,\n action: s.root.has(key) ? \"edit\" : \"create\",\n source: \"operation\",\n });\n }\n return \"OK\";\n }\n\n case \"del\": {\n const key = String(args.key ?? \"\");\n if (!s.root.has(key)) return \"Not found\";\n s.root.delete(key);\n this._recordHistory(handle, \"delete\", key);\n this.emitChange?.({ path: key, action: \"delete\", source: \"operation\" });\n return \"Deleted\";\n }\n\n default:\n throw new Error(`YjsConnector has no operation: ${operation}`);\n }\n }\n\n // ── Private helpers ────────────────────────────────────────────────────────\n\n private async _getY(): Promise<any> {\n const yjsPkg = \"yjs\";\n return import(/* @vite-ignore */ yjsPkg);\n }\n\n /** Convert a JS value to a Yjs-compatible value. */\n private _toYjsValue(Y: any, value: unknown): any {\n if (value === null || value === undefined) return value;\n if (typeof value !== \"object\") return value;\n if (Array.isArray(value)) {\n const arr = new Y.Array();\n for (const item of value) arr.push([this._toYjsValue(Y, item)]);\n return arr;\n }\n const map = new Y.Map();\n for (const [k, v] of Object.entries(value as Record<string, unknown>)) {\n map.set(k, this._toYjsValue(Y, v));\n }\n return map;\n }\n\n /** Convert a Yjs value back to plain JS. */\n private _fromYjsValue(value: any): unknown {\n if (value === null || value === undefined) return value;\n if (typeof value?.toJSON === \"function\") return value.toJSON();\n return value;\n }\n\n private _recordHistory(handle: ConnectorHandle, op: string, key: string): void {\n const s = this.s(handle);\n s.history.push({ op, key, at: new Date().toISOString() });\n if (s.history.length > 500) s.history = s.history.slice(-500);\n }\n}\n\n/**\n * Creates a new YjsConnector instance.\n * @returns Configured YjsConnector ready to be registered in the connector registry.\n */\nexport function createConnector(): YjsConnector {\n return new YjsConnector();\n}\n\n/**\n * Plugin-registry target wrapping the Yjs connector factory. Connectors take no\n * construct-time config, so `configSchema` is a passthrough and `create()`\n * returns a fresh instance; lifecycle config arrives via `Connector.connect()`.\n */\nexport const yjsConnectorTarget: ConnectorTarget = {\n id: \"yjs\",\n displayName: \"Yjs\",\n apiVersion: 1,\n configSchema: z.unknown(),\n create: (_config, _ctx) => new YjsConnector(),\n};\n","/**\n * `@skaile/connector-yjs` — Yjs CRDT connector for `@skaile/workspaces`.\n *\n * Install via `skaile plugin install @skaile/connector-yjs`; the plugin loader\n * calls `register(pluginRegistry)` at startup so `yjs` becomes resolvable as a\n * connector driver.\n */\n\nimport type { PluginRegistry } from \"@skaile/workspaces/plugin-registry\";\nimport { yjsConnectorTarget } from \"./target.js\";\n\nexport { yjsConnectorTarget, YjsConnector, createConnector } from \"./target.js\";\n\n/** Register this plugin's connector target with a registry. */\nexport function register(registry: PluginRegistry): void {\n registry.register(\"connector\", yjsConnectorTarget);\n}\n"],"mappings":"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","names":[]}
|
|
1
|
+
{"version":3,"sources":["../src/target.ts","../src/index.ts"],"sourcesContent":["/**\n * YjsConnector — CRDT-based shared document state via Yjs.\n * Not mountable. Provides real-time collaborative state that merges\n * concurrent edits from multiple agents and UI clients.\n *\n * Requires `yjs` (a runtime dependency of this package).\n */\n\nimport type {\n ConnectContext,\n ConnectorContent,\n ConnectorDeclaration,\n ConnectorEntry,\n ConnectorHandle,\n ListOptions,\n OperationDescriptor,\n SearchOptions,\n SearchResult,\n ToolFace,\n} from \"@skaile/workspaces/connectors\";\nimport { AbstractConnector } from \"@skaile/workspaces/connectors\";\nimport type { ConnectorTarget } from \"@skaile/workspaces/plugin-registry\";\nimport * as z from \"zod\";\n\ninterface YjsState {\n doc: any; // Y.Doc\n root: any; // Y.Map (root map)\n history: Array<{ op: string; key: string; at: string }>;\n onChange?: (snapshot: Record<string, unknown>, changed: string[]) => void;\n}\n\n/**\n * Connector for Yjs CRDT-based shared document state — enables real-time collaborative state that merges concurrent edits from multiple agents and UI clients.\n * Compatible with Hocuspocus and y-websocket provider connections via `getDoc()`. Requires the `yjs` package.\n */\nexport class YjsConnector extends AbstractConnector {\n readonly name = \"yjs\";\n\n tools: ToolFace;\n\n constructor() {\n super();\n this.tools = {\n read: (handle, path) => this._read(handle, path),\n write: (handle, path, content) => this._write(handle, path, content),\n delete: (handle, path) => this._delete(handle, path),\n list: (handle, path, options) => this._list(handle, path, options),\n search: (handle, query, options) => this._search(handle, query, options),\n describeOperations: (handle) => this._describeOperations(handle),\n executeOp: (handle, operation, args) => this._executeOp(handle, operation, args),\n };\n }\n\n async connect(declaration: ConnectorDeclaration, _ctx: ConnectContext): Promise<ConnectorHandle> {\n this.log.info(\"connect\", { id: declaration.id });\n const yjsPkg = \"yjs\";\n const Y = (await import(/* @vite-ignore */ yjsPkg)) as any;\n\n const doc = new Y.Doc();\n const root = doc.getMap(\"root\");\n\n // Apply initial values\n const initial = declaration.options?.initial as Record<string, unknown> | undefined;\n if (initial) {\n doc.transact(() => {\n for (const [key, value] of Object.entries(initial)) {\n root.set(key, this._toYjsValue(Y, value));\n }\n });\n }\n\n const state: YjsState = { doc, root, history: [] };\n\n // Observe changes\n root.observe((event: any) => {\n const changed: string[] = [];\n event.changes.keys.forEach((_change: any, key: string) => {\n changed.push(key);\n });\n if (changed.length > 0 && state.onChange) {\n state.onChange(root.toJSON(), changed);\n }\n });\n\n return this.makeHandle(declaration, state);\n }\n\n async disconnect(handle: ConnectorHandle): Promise<void> {\n this.log.info(\"disconnect\");\n const s = handle.state as YjsState;\n try {\n s.doc.destroy();\n } finally {\n handle.status = \"disconnected\";\n }\n }\n\n private s(handle: ConnectorHandle): YjsState {\n return handle.state as YjsState;\n }\n\n /**\n * Get the Y.Doc for external provider connections (Hocuspocus, y-websocket).\n */\n getDoc(handle: ConnectorHandle): unknown {\n return this.s(handle).doc;\n }\n\n /**\n * Register a callback fired on every change to the root map.\n */\n onDocChange(\n handle: ConnectorHandle,\n cb: (snapshot: Record<string, unknown>, changedKeys: string[]) => void,\n ): void {\n this.s(handle).onChange = cb;\n }\n\n // ── Tool face methods (private, wired in constructor) ─────────────────────\n\n private async _read(handle: ConnectorHandle, path: string): Promise<ConnectorContent | null> {\n const root = this.s(handle).root;\n const value = root.get(path);\n if (value === undefined) return null;\n const resolved = this._fromYjsValue(value);\n return { data: JSON.stringify(resolved), contentType: \"application/json\" };\n }\n\n private async _write(\n handle: ConnectorHandle,\n path: string,\n content: ConnectorContent,\n ): Promise<void> {\n const data = typeof content.data === \"string\" ? content.data : content.data.toString(\"utf-8\");\n let value: unknown;\n try {\n value = JSON.parse(data);\n } catch {\n value = data;\n }\n\n const Y = await this._getY();\n const root = this.s(handle).root;\n // Capture existence before the transact so we report create vs edit correctly.\n const hadKey = root.has(path);\n this.s(handle).doc.transact(() => {\n root.set(path, this._toYjsValue(Y, value));\n });\n this._recordHistory(handle, \"set\", path);\n this.emitChange?.({ path, action: hadKey ? \"edit\" : \"create\", source: \"operation\" });\n }\n\n private async _delete(handle: ConnectorHandle, path: string): Promise<boolean> {\n const root = this.s(handle).root;\n if (!root.has(path)) return false;\n root.delete(path);\n this._recordHistory(handle, \"delete\", path);\n return true;\n }\n\n private async _list(\n handle: ConnectorHandle,\n _path?: string,\n options?: ListOptions,\n ): Promise<ConnectorEntry[]> {\n const root = this.s(handle).root;\n const entries: ConnectorEntry[] = [];\n for (const key of root.keys()) {\n if (options?.limit && entries.length >= options.limit) break;\n entries.push({ name: key, path: key, type: \"key\" });\n }\n return entries;\n }\n\n private async _search(\n handle: ConnectorHandle,\n query: string,\n options?: SearchOptions,\n ): Promise<SearchResult[]> {\n const root = this.s(handle).root;\n const results: SearchResult[] = [];\n for (const key of root.keys()) {\n if (options?.maxResults && results.length >= options.maxResults) break;\n const value = root.get(key);\n const str = JSON.stringify(this._fromYjsValue(value));\n if (key.includes(query) || str.includes(query)) {\n results.push({ path: key, snippet: str.slice(0, 200) });\n }\n }\n return results;\n }\n\n private _describeOperations(handle: ConnectorHandle): OperationDescriptor[] {\n const ops: OperationDescriptor[] = [\n {\n name: \"snapshot\",\n description: \"Get the full document snapshot (all keys and values).\",\n accessLevel: \"read\",\n args: [],\n },\n {\n name: \"get\",\n description: \"Get a single value by key.\",\n accessLevel: \"read\",\n args: [{ name: \"key\", type: \"string\", required: true, description: \"Key name\" }],\n },\n {\n name: \"keys\",\n description: \"List all keys in the document.\",\n accessLevel: \"read\",\n args: [],\n },\n {\n name: \"history\",\n description: \"Get the local operation history.\",\n accessLevel: \"read\",\n args: [{ name: \"limit\", type: \"number\", required: false, description: \"Max entries\" }],\n },\n ];\n\n if (handle.access === \"read-write\") {\n ops.push(\n {\n name: \"set\",\n description: \"Set a key to a value. Merges with concurrent writes via CRDT.\",\n accessLevel: \"write\",\n args: [\n { name: \"key\", type: \"string\", required: true, description: \"Key name\" },\n {\n name: \"value\",\n type: \"string\",\n required: true,\n description: \"Value (JSON-encoded for objects)\",\n },\n ],\n examples: [\n \"set --key theme --value '\\\"dark\\\"'\",\n \"set --key config --value '{\\\"debug\\\":true}'\",\n ],\n },\n {\n name: \"merge\",\n description: \"Merge an object into the document (each key set independently via CRDT).\",\n accessLevel: \"write\",\n args: [\n { name: \"data\", type: \"json\", required: true, description: \"JSON object to merge\" },\n ],\n },\n {\n name: \"del\",\n description: \"Delete a key from the document.\",\n accessLevel: \"write\",\n args: [{ name: \"key\", type: \"string\", required: true, description: \"Key to delete\" }],\n },\n );\n }\n\n return ops;\n }\n\n private async _executeOp(\n handle: ConnectorHandle,\n operation: string,\n args: Record<string, unknown>,\n ): Promise<string> {\n const s = this.s(handle);\n const Y = await this._getY();\n\n switch (operation) {\n case \"snapshot\":\n return this._opSnapshot(s);\n\n case \"get\":\n return this._opGet(s, args);\n\n case \"keys\":\n return this._opKeys(s);\n\n case \"history\":\n return this._opHistory(s, args);\n\n case \"set\":\n return this._opSet(handle, s, Y, args);\n\n case \"merge\":\n return this._opMerge(handle, s, Y, args);\n\n case \"del\":\n return this._opDel(handle, s, args);\n\n default:\n throw new Error(`YjsConnector has no operation: ${operation}`);\n }\n }\n\n private _opSnapshot(s: YjsState): string {\n return JSON.stringify(s.root.toJSON(), null, 2);\n }\n\n private _opGet(s: YjsState, args: Record<string, unknown>): string {\n const key = String(args.key ?? \"\");\n const value = s.root.get(key);\n return value !== undefined ? JSON.stringify(this._fromYjsValue(value)) : \"\";\n }\n\n private _opKeys(s: YjsState): string {\n return JSON.stringify([...s.root.keys()]);\n }\n\n private _opHistory(s: YjsState, args: Record<string, unknown>): string {\n const limit = Number(args.limit ?? 50);\n return JSON.stringify(s.history.slice(-limit), null, 2);\n }\n\n private _opSet(\n handle: ConnectorHandle,\n s: YjsState,\n Y: any,\n args: Record<string, unknown>,\n ): string {\n const key = String(args.key ?? \"\");\n let value: unknown = args.value;\n if (typeof value === \"string\") {\n try {\n value = JSON.parse(value);\n } catch {\n /* keep as string */\n }\n }\n const hadKey = s.root.has(key);\n s.doc.transact(() => {\n s.root.set(key, this._toYjsValue(Y, value));\n });\n this._recordHistory(handle, \"set\", key);\n this.emitChange?.({ path: key, action: hadKey ? \"edit\" : \"create\", source: \"operation\" });\n return \"OK\";\n }\n\n private _opMerge(\n handle: ConnectorHandle,\n s: YjsState,\n Y: any,\n args: Record<string, unknown>,\n ): string {\n let data = args.data;\n if (typeof data === \"string\") data = JSON.parse(data);\n if (typeof data !== \"object\" || data === null) return \"Error: data must be a JSON object\";\n // Snapshot existence before the transact: the write loop populates every\n // key, so a post-write has() check would always report \"edit\".\n const existed = new Set(\n Object.keys(data as Record<string, unknown>).filter((k) => s.root.has(k)),\n );\n s.doc.transact(() => {\n for (const [key, value] of Object.entries(data as Record<string, unknown>)) {\n s.root.set(key, this._toYjsValue(Y, value));\n }\n });\n for (const key of Object.keys(data as Record<string, unknown>)) {\n this._recordHistory(handle, \"set\", key);\n this.emitChange?.({\n path: key,\n action: existed.has(key) ? \"edit\" : \"create\",\n source: \"operation\",\n });\n }\n return \"OK\";\n }\n\n private _opDel(handle: ConnectorHandle, s: YjsState, args: Record<string, unknown>): string {\n const key = String(args.key ?? \"\");\n if (!s.root.has(key)) return \"Not found\";\n s.root.delete(key);\n this._recordHistory(handle, \"delete\", key);\n this.emitChange?.({ path: key, action: \"delete\", source: \"operation\" });\n return \"Deleted\";\n }\n\n // ── Private helpers ────────────────────────────────────────────────────────\n\n private async _getY(): Promise<any> {\n const yjsPkg = \"yjs\";\n return import(/* @vite-ignore */ yjsPkg);\n }\n\n /** Convert a JS value to a Yjs-compatible value. */\n private _toYjsValue(Y: any, value: unknown): any {\n if (value === null || value === undefined) return value;\n if (typeof value !== \"object\") return value;\n if (Array.isArray(value)) {\n const arr = new Y.Array();\n for (const item of value) arr.push([this._toYjsValue(Y, item)]);\n return arr;\n }\n const map = new Y.Map();\n for (const [k, v] of Object.entries(value as Record<string, unknown>)) {\n map.set(k, this._toYjsValue(Y, v));\n }\n return map;\n }\n\n /** Convert a Yjs value back to plain JS. */\n private _fromYjsValue(value: any): unknown {\n if (value === null || value === undefined) return value;\n if (typeof value?.toJSON === \"function\") return value.toJSON();\n return value;\n }\n\n private _recordHistory(handle: ConnectorHandle, op: string, key: string): void {\n const s = this.s(handle);\n s.history.push({ op, key, at: new Date().toISOString() });\n if (s.history.length > 500) s.history = s.history.slice(-500);\n }\n}\n\n/**\n * Creates a new YjsConnector instance.\n * @returns Configured YjsConnector ready to be registered in the connector registry.\n */\nexport function createConnector(): YjsConnector {\n return new YjsConnector();\n}\n\n/**\n * Plugin-registry target wrapping the Yjs connector factory. Connectors take no\n * construct-time config, so `configSchema` is a passthrough and `create()`\n * returns a fresh instance; lifecycle config arrives via `Connector.connect()`.\n */\nexport const yjsConnectorTarget: ConnectorTarget = {\n id: \"yjs\",\n displayName: \"Yjs\",\n apiVersion: 1,\n configSchema: z.unknown(),\n create: (_config, _ctx) => new YjsConnector(),\n};\n","/**\n * `@skaile/connector-yjs` — Yjs CRDT connector for `@skaile/workspaces`.\n *\n * Install via `skaile plugin install @skaile/connector-yjs`; the plugin loader\n * calls `register(pluginRegistry)` at startup so `yjs` becomes resolvable as a\n * connector driver.\n */\n\nimport type { PluginRegistry } from \"@skaile/workspaces/plugin-registry\";\nimport { yjsConnectorTarget } from \"./target.js\";\n\nexport { createConnector, YjsConnector, yjsConnectorTarget } from \"./target.js\";\n\n/** Register this plugin's connector target with a registry. */\nexport function register(registry: PluginRegistry): void {\n registry.register(\"connector\", yjsConnectorTarget);\n}\n"],"mappings":"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","names":[]}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@skaile/connector-yjs",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.2",
|
|
4
4
|
"description": "Yjs CRDT connector for @skaile/workspaces",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./dist/index.js",
|
|
@@ -25,7 +25,7 @@
|
|
|
25
25
|
"@skaile/workspaces": ">=0.22.0-0"
|
|
26
26
|
},
|
|
27
27
|
"devDependencies": {
|
|
28
|
-
"@skaile/workspaces": "^0.
|
|
28
|
+
"@skaile/workspaces": "^0.30.1"
|
|
29
29
|
},
|
|
30
30
|
"scripts": {
|
|
31
31
|
"build": "tsup",
|