@symbo.ls/sdk 3.1.2 → 3.2.3

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.
Files changed (65) hide show
  1. package/README.md +2 -2
  2. package/dist/cjs/config/environment.js +5 -21
  3. package/dist/cjs/index.js +6 -26
  4. package/dist/cjs/services/AIService.js +3 -3
  5. package/dist/cjs/services/CollabService.js +420 -0
  6. package/dist/cjs/services/CoreService.js +651 -107
  7. package/dist/cjs/services/SocketService.js +207 -59
  8. package/dist/cjs/services/index.js +5 -13
  9. package/dist/cjs/state/RootStateManager.js +86 -0
  10. package/dist/cjs/state/rootEventBus.js +65 -0
  11. package/dist/cjs/utils/CollabClient.js +157 -0
  12. package/dist/cjs/utils/TokenManager.js +62 -27
  13. package/dist/cjs/utils/jsonDiff.js +103 -0
  14. package/dist/cjs/utils/services.js +129 -88
  15. package/dist/cjs/utils/symstoryClient.js +5 -5
  16. package/dist/esm/config/environment.js +5 -21
  17. package/dist/esm/index.js +20459 -9286
  18. package/dist/esm/services/AIService.js +3 -3
  19. package/dist/esm/services/BasedService.js +5 -21
  20. package/dist/esm/services/CollabService.js +18028 -0
  21. package/dist/esm/services/CoreService.js +718 -155
  22. package/dist/esm/services/SocketService.js +323 -58
  23. package/dist/esm/services/SymstoryService.js +10 -26
  24. package/dist/esm/services/index.js +20305 -9158
  25. package/dist/esm/state/RootStateManager.js +102 -0
  26. package/dist/esm/state/rootEventBus.js +47 -0
  27. package/dist/esm/utils/CollabClient.js +17483 -0
  28. package/dist/esm/utils/TokenManager.js +62 -27
  29. package/dist/esm/utils/jsonDiff.js +6096 -0
  30. package/dist/esm/utils/services.js +129 -88
  31. package/dist/esm/utils/symstoryClient.js +10 -26
  32. package/dist/node/config/environment.js +5 -21
  33. package/dist/node/index.js +10 -34
  34. package/dist/node/services/AIService.js +3 -3
  35. package/dist/node/services/CollabService.js +401 -0
  36. package/dist/node/services/CoreService.js +651 -107
  37. package/dist/node/services/SocketService.js +197 -59
  38. package/dist/node/services/index.js +5 -13
  39. package/dist/node/state/RootStateManager.js +57 -0
  40. package/dist/node/state/rootEventBus.js +46 -0
  41. package/dist/node/utils/CollabClient.js +128 -0
  42. package/dist/node/utils/TokenManager.js +62 -27
  43. package/dist/node/utils/jsonDiff.js +74 -0
  44. package/dist/node/utils/services.js +129 -88
  45. package/dist/node/utils/symstoryClient.js +5 -5
  46. package/package.json +12 -6
  47. package/src/config/environment.js +5 -19
  48. package/src/index.js +9 -31
  49. package/src/services/AIService.js +3 -3
  50. package/src/services/BasedService.js +1 -0
  51. package/src/services/CollabService.js +491 -0
  52. package/src/services/CoreService.js +715 -110
  53. package/src/services/SocketService.js +227 -59
  54. package/src/services/index.js +6 -13
  55. package/src/state/RootStateManager.js +71 -0
  56. package/src/state/rootEventBus.js +48 -0
  57. package/src/utils/CollabClient.js +161 -0
  58. package/src/utils/TokenManager.js +68 -30
  59. package/src/utils/jsonDiff.js +109 -0
  60. package/src/utils/services.js +140 -88
  61. package/src/utils/symstoryClient.js +5 -5
  62. package/dist/cjs/services/SocketIOService.js +0 -307
  63. package/dist/esm/services/SocketIOService.js +0 -470
  64. package/dist/node/services/SocketIOService.js +0 -278
  65. package/src/services/SocketIOService.js +0 -334
@@ -0,0 +1,401 @@
1
+ import { BaseService } from "./BaseService.js";
2
+ import { CollabClient } from "../utils/CollabClient.js";
3
+ import { RootStateManager } from "../state/RootStateManager.js";
4
+ import { rootBus } from "../state/rootEventBus.js";
5
+ class CollabService extends BaseService {
6
+ constructor(config) {
7
+ super(config);
8
+ this._client = null;
9
+ this._stateManager = null;
10
+ this._connected = false;
11
+ this._undoStack = [];
12
+ this._redoStack = [];
13
+ this._isUndoRedo = false;
14
+ this._pendingOps = [];
15
+ }
16
+ init({ context }) {
17
+ if (context == null ? void 0 : context.state) {
18
+ try {
19
+ this._stateManager = new RootStateManager(context.state);
20
+ } catch (err) {
21
+ this._setError(err);
22
+ throw err;
23
+ }
24
+ }
25
+ this._setReady();
26
+ }
27
+ /**
28
+ * Overridden to re-initialise the state manager once the root state becomes
29
+ * available via a subsequent SDK `updateContext()` call.
30
+ */
31
+ updateContext(context = {}) {
32
+ super.updateContext(context);
33
+ if (context.state) {
34
+ this._stateManager = new RootStateManager(context.state);
35
+ }
36
+ }
37
+ /**
38
+ * Ensure that the state manager exists. This is called right before any
39
+ * operation that requires access to the root state (e.g. `connect()`).
40
+ * Throws an explicit error if the root state is still missing so that the
41
+ * caller can react accordingly.
42
+ */
43
+ _ensureStateManager() {
44
+ var _a;
45
+ if (!this._stateManager) {
46
+ if (!((_a = this._context) == null ? void 0 : _a.state)) {
47
+ throw new Error("[CollabService] Cannot operate without root state");
48
+ }
49
+ this._stateManager = new RootStateManager(this._context.state);
50
+ }
51
+ }
52
+ /* ---------- Connection Management ---------- */
53
+ async connect(options = {}) {
54
+ var _a, _b, _c, _d, _e;
55
+ this._ensureStateManager();
56
+ const {
57
+ authToken: jwt,
58
+ projectId,
59
+ branch = "main",
60
+ pro
61
+ } = {
62
+ ...this._context,
63
+ ...options
64
+ };
65
+ console.log(jwt, projectId, branch, pro);
66
+ if (!projectId) {
67
+ throw new Error("projectId is required for CollabService connection");
68
+ }
69
+ if (this._client) {
70
+ await this.disconnect();
71
+ }
72
+ try {
73
+ this._client = new CollabClient({
74
+ jwt,
75
+ projectId,
76
+ branch,
77
+ live: Boolean(pro)
78
+ });
79
+ await new Promise((resolve) => {
80
+ var _a2, _b2;
81
+ if ((_a2 = this._client.socket) == null ? void 0 : _a2.connected) {
82
+ resolve();
83
+ } else {
84
+ (_b2 = this._client.socket) == null ? void 0 : _b2.once("connect", resolve);
85
+ }
86
+ });
87
+ console.log("[CollabService] socket connected");
88
+ (_a = this._client.socket) == null ? void 0 : _a.on("ops", ({ changes }) => {
89
+ console.log(`ops event`);
90
+ console.log(changes);
91
+ this._stateManager.applyChanges(changes, { fromSocket: true });
92
+ });
93
+ (_b = this._client.socket) == null ? void 0 : _b.on("commit", ({ version }) => {
94
+ this._stateManager.setVersion(version);
95
+ rootBus.emit("checkpoint:done", { version, origin: "auto" });
96
+ });
97
+ (_c = this._client.socket) == null ? void 0 : _c.on("clients", this._handleClientsEvent.bind(this));
98
+ (_d = this._client.socket) == null ? void 0 : _d.on("presence", this._handleClientsEvent.bind(this));
99
+ (_e = this._client.socket) == null ? void 0 : _e.on("cursor", this._handleCursorEvent.bind(this));
100
+ if (this._pendingOps.length) {
101
+ console.log(
102
+ `[CollabService] Flushing ${this._pendingOps.length} offline operation batch(es)`
103
+ );
104
+ this._pendingOps.forEach(({ tuples }) => {
105
+ this.socket.emit("ops", { changes: tuples, ts: Date.now() });
106
+ });
107
+ this._pendingOps.length = 0;
108
+ }
109
+ this._connected = true;
110
+ console.log("[CollabService] Connected to project:", projectId);
111
+ } catch (err) {
112
+ console.error("[CollabService] Connection failed:", err);
113
+ throw err;
114
+ }
115
+ }
116
+ disconnect() {
117
+ var _a;
118
+ if ((_a = this._client) == null ? void 0 : _a.socket) {
119
+ this._client.socket.disconnect();
120
+ }
121
+ this._client = null;
122
+ this._connected = false;
123
+ console.log("[CollabService] Disconnected");
124
+ }
125
+ isConnected() {
126
+ var _a, _b;
127
+ return this._connected && ((_b = (_a = this._client) == null ? void 0 : _a.socket) == null ? void 0 : _b.connected);
128
+ }
129
+ /* convenient shortcuts */
130
+ get ydoc() {
131
+ var _a;
132
+ return (_a = this._client) == null ? void 0 : _a.ydoc;
133
+ }
134
+ get socket() {
135
+ var _a;
136
+ return (_a = this._client) == null ? void 0 : _a.socket;
137
+ }
138
+ toggleLive(f) {
139
+ var _a;
140
+ (_a = this._client) == null ? void 0 : _a.toggleLive(f);
141
+ }
142
+ sendCursor(d) {
143
+ var _a;
144
+ (_a = this._client) == null ? void 0 : _a.sendCursor(d);
145
+ }
146
+ sendPresence(d) {
147
+ var _a;
148
+ (_a = this._client) == null ? void 0 : _a.sendPresence(d);
149
+ }
150
+ /* ---------- data helpers ---------- */
151
+ updateData(tuples, options = {}) {
152
+ var _a;
153
+ this._ensureStateManager();
154
+ const { isUndo = false, isRedo = false } = options;
155
+ if (!isUndo && !isRedo && !this._isUndoRedo) {
156
+ this._trackForUndo(tuples, options);
157
+ }
158
+ this._stateManager.applyChanges(tuples, { ...options });
159
+ if (!this.isConnected()) {
160
+ console.warn("[CollabService] Not connected, queuing real-time update");
161
+ this._pendingOps.push({ tuples, options });
162
+ return;
163
+ }
164
+ if ((_a = this.socket) == null ? void 0 : _a.connected) {
165
+ this.socket.emit("ops", { changes: tuples, ts: Date.now() });
166
+ }
167
+ return { success: true };
168
+ }
169
+ _trackForUndo(tuples, options) {
170
+ var _a;
171
+ const undoOperations = tuples.map((tuple) => {
172
+ const [action, path] = tuple;
173
+ const currentValue = this._getValueAtPath(path);
174
+ if (action === "delete") {
175
+ return ["update", path, currentValue];
176
+ }
177
+ if (typeof currentValue !== "undefined") {
178
+ return ["update", path, currentValue];
179
+ }
180
+ return ["delete", path];
181
+ });
182
+ this._undoStack.push({
183
+ operations: undoOperations,
184
+ originalOperations: tuples,
185
+ options,
186
+ timestamp: Date.now()
187
+ });
188
+ this._redoStack.length = 0;
189
+ const maxUndoSteps = ((_a = this._options) == null ? void 0 : _a.maxUndoSteps) || 50;
190
+ if (this._undoStack.length > maxUndoSteps) {
191
+ this._undoStack.shift();
192
+ }
193
+ }
194
+ _getValueAtPath(path) {
195
+ var _a;
196
+ const state = (_a = this._stateManager) == null ? void 0 : _a.root;
197
+ if (!state || !state.getByPath) {
198
+ return null;
199
+ }
200
+ try {
201
+ return state.getByPath(path);
202
+ } catch (error) {
203
+ console.warn("[CollabService] Could not get value at path:", path, error);
204
+ return null;
205
+ }
206
+ }
207
+ undo() {
208
+ if (!this._undoStack.length) {
209
+ throw new Error("Nothing to undo");
210
+ }
211
+ if (!this.isConnected()) {
212
+ console.warn("[CollabService] Not connected, cannot undo");
213
+ return;
214
+ }
215
+ const undoItem = this._undoStack.pop();
216
+ const { operations, originalOperations, options } = undoItem;
217
+ this._redoStack.push({
218
+ operations: originalOperations,
219
+ originalOperations: operations,
220
+ options,
221
+ timestamp: Date.now()
222
+ });
223
+ this._isUndoRedo = true;
224
+ try {
225
+ this.updateData(operations, {
226
+ ...options,
227
+ isUndo: true,
228
+ message: `Undo: ${options.message || "operation"}`
229
+ });
230
+ } finally {
231
+ this._isUndoRedo = false;
232
+ }
233
+ return operations;
234
+ }
235
+ redo() {
236
+ if (!this._redoStack.length) {
237
+ throw new Error("Nothing to redo");
238
+ }
239
+ if (!this.isConnected()) {
240
+ console.warn("[CollabService] Not connected, cannot redo");
241
+ return;
242
+ }
243
+ const redoItem = this._redoStack.pop();
244
+ const { operations, originalOperations, options } = redoItem;
245
+ this._undoStack.push({
246
+ operations: originalOperations,
247
+ originalOperations: operations,
248
+ options,
249
+ timestamp: Date.now()
250
+ });
251
+ this._isUndoRedo = true;
252
+ try {
253
+ this.updateData(operations, {
254
+ ...options,
255
+ isRedo: true,
256
+ message: `Redo: ${options.message || "operation"}`
257
+ });
258
+ } finally {
259
+ this._isUndoRedo = false;
260
+ }
261
+ return operations;
262
+ }
263
+ /* ---------- Undo/Redo State ---------- */
264
+ canUndo() {
265
+ return this._undoStack.length > 0;
266
+ }
267
+ canRedo() {
268
+ return this._redoStack.length > 0;
269
+ }
270
+ getUndoStackSize() {
271
+ return this._undoStack.length;
272
+ }
273
+ getRedoStackSize() {
274
+ return this._redoStack.length;
275
+ }
276
+ clearUndoHistory() {
277
+ this._undoStack.length = 0;
278
+ this._redoStack.length = 0;
279
+ }
280
+ addItem(type, data, opts = {}) {
281
+ const { value, ...schema } = data;
282
+ const tuples = [
283
+ ["update", [type, data.key], value],
284
+ ["update", ["schema", type, data.key], schema],
285
+ ...opts.additionalChanges || []
286
+ ];
287
+ return this.updateData(tuples, opts);
288
+ }
289
+ addMultipleItems(items, opts = {}) {
290
+ const tuples = [];
291
+ items.forEach(([type, data]) => {
292
+ const { value, ...schema } = data;
293
+ tuples.push(
294
+ ["update", [type, data.key], value],
295
+ ["update", ["schema", type, data.key], schema]
296
+ );
297
+ });
298
+ this.updateData([...tuples, ...opts.additionalChanges || []], {
299
+ message: `Created ${tuples.length} items`,
300
+ ...opts
301
+ });
302
+ return tuples;
303
+ }
304
+ updateItem(type, data, opts = {}) {
305
+ const { value, ...schema } = data;
306
+ const tuples = [
307
+ ["update", [type, data.key], value],
308
+ ["update", ["schema", type, data.key], schema]
309
+ ];
310
+ return this.updateData(tuples, opts);
311
+ }
312
+ deleteItem(type, key, opts = {}) {
313
+ const tuples = [
314
+ ["delete", [type, key]],
315
+ ["delete", ["schema", type, key]],
316
+ ...opts.additionalChanges || []
317
+ ];
318
+ return this.updateData(tuples, {
319
+ message: `Deleted ${key} from ${type}`,
320
+ ...opts
321
+ });
322
+ }
323
+ /* ---------- socket event helpers ---------- */
324
+ /**
325
+ * Handle "clients" or "presence" events coming from the collab socket.
326
+ * The backend sends the full `clients` object which we directly patch to
327
+ * the root state, mimicking the legacy SocketService behaviour so that
328
+ * existing UI components keep working unmodified.
329
+ */
330
+ _handleClientsEvent(data = {}) {
331
+ var _a;
332
+ const root = (_a = this._stateManager) == null ? void 0 : _a.root;
333
+ if (root && typeof root.replace === "function") {
334
+ root.replace(
335
+ { clients: data },
336
+ {
337
+ fromSocket: true,
338
+ preventUpdate: true
339
+ }
340
+ );
341
+ }
342
+ }
343
+ /**
344
+ * Handle granular cursor updates coming from the socket.
345
+ * Expected payload: { userId, positions?, chosenPos?, ... }
346
+ * Only the provided fields are patched into the state tree.
347
+ */
348
+ _handleCursorEvent(payload = {}) {
349
+ const { userId, positions, chosenPos, ...rest } = payload || {};
350
+ if (!userId) {
351
+ return;
352
+ }
353
+ const tuples = [];
354
+ if (positions) {
355
+ tuples.push([
356
+ "update",
357
+ ["canvas", "clients", userId, "positions"],
358
+ positions
359
+ ]);
360
+ }
361
+ if (chosenPos) {
362
+ tuples.push([
363
+ "update",
364
+ ["canvas", "clients", userId, "chosenPos"],
365
+ chosenPos
366
+ ]);
367
+ }
368
+ if (Object.keys(rest).length) {
369
+ tuples.push(["update", ["canvas", "clients", userId], rest]);
370
+ }
371
+ if (tuples.length) {
372
+ this._stateManager.applyChanges(tuples, { fromSocket: true });
373
+ }
374
+ }
375
+ /* ---------- Manual checkpoint ---------- */
376
+ /**
377
+ * Manually request a checkpoint / commit of buffered operations on the server.
378
+ * Resolves with the new version number once the backend confirms via the
379
+ * regular "commit" event.
380
+ */
381
+ checkpoint() {
382
+ if (!this.isConnected()) {
383
+ console.warn("[CollabService] Not connected, cannot request checkpoint");
384
+ return Promise.reject(new Error("Not connected"));
385
+ }
386
+ return new Promise((resolve) => {
387
+ var _a, _b;
388
+ const handler = ({ version }) => {
389
+ var _a2;
390
+ (_a2 = this.socket) == null ? void 0 : _a2.off("commit", handler);
391
+ rootBus.emit("checkpoint:done", { version, origin: "manual" });
392
+ resolve(version);
393
+ };
394
+ (_a = this.socket) == null ? void 0 : _a.once("commit", handler);
395
+ (_b = this.socket) == null ? void 0 : _b.emit("checkpoint");
396
+ });
397
+ }
398
+ }
399
+ export {
400
+ CollabService
401
+ };