@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
package/README.md CHANGED
@@ -12,7 +12,7 @@ import { SDK } from '@symbo.ls/sdk'
12
12
 
13
13
  const sdk = new SDK({
14
14
  useNewServices: true, // Use new service implementations
15
- baseUrl: 'https://api.symbols.app',
15
+ apiUrl: 'https://api.symbols.app',
16
16
  socketUrl: 'https://api.symbols.app',
17
17
  timeout: 30000,
18
18
  retryAttempts: 3,
@@ -396,7 +396,7 @@ sdk.destroy()
396
396
  ```javascript
397
397
  const options = {
398
398
  useNewServices: true,
399
- baseUrl: 'https://api.symbols.app',
399
+ apiUrl: 'https://api.symbols.app',
400
400
  socketUrl: 'https://api.symbols.app',
401
401
  timeout: 30000,
402
402
  retryAttempts: 3,
@@ -36,12 +36,8 @@ const CONFIG = {
36
36
  // Environment-specific configurations
37
37
  local: {
38
38
  // local
39
- baseUrl: "http://localhost:8080",
40
- // For symstory api
41
39
  socketUrl: "http://localhost:8080",
42
40
  // For socket api
43
- routerUrl: "http://localhost:8080",
44
- // For router api
45
41
  apiUrl: "http://localhost:8080",
46
42
  // For server api
47
43
  basedEnv: "development",
@@ -59,29 +55,25 @@ const CONFIG = {
59
55
  }
60
56
  },
61
57
  development: {
62
- baseUrl: "https://dev.api.symbols.app",
63
58
  socketUrl: "https://dev.api.symbols.app",
64
- routerUrl: "https://dev.api.symbols.app",
65
59
  apiUrl: "https://dev.api.symbols.app",
66
- basedEnv: "development",
67
- basedProject: "platform-v2-sm",
68
- basedOrg: "symbols",
69
60
  githubClientId: "Ov23liHxyWFBxS8f1gnF"
70
61
  },
71
62
  testing: {
72
- baseUrl: "https://test.api.symbols.app",
73
63
  socketUrl: "https://test.api.symbols.app",
74
- routerUrl: "https://test.api.symbols.app",
75
64
  apiUrl: "https://test.api.symbols.app",
76
65
  basedEnv: "testing",
77
66
  basedProject: "platform-v2-sm",
78
67
  basedOrg: "symbols",
79
68
  githubClientId: "Ov23liHxyWFBxS8f1gnF"
80
69
  },
70
+ upcoming: {
71
+ socketUrl: "https://upcoming.api.symbols.app",
72
+ apiUrl: "https://upcoming.api.symbols.app",
73
+ githubClientId: "Ov23liWF7NvdZ056RV5J"
74
+ },
81
75
  staging: {
82
- baseUrl: "https://staging.api.symbols.app",
83
76
  socketUrl: "https://staging.api.symbols.app",
84
- routerUrl: "https://staging.api.symbols.app",
85
77
  apiUrl: "https://staging.api.symbols.app",
86
78
  basedEnv: "staging",
87
79
  basedProject: "platform-v2-sm",
@@ -89,9 +81,7 @@ const CONFIG = {
89
81
  githubClientId: "Ov23ligwZDQVD0VfuWNa"
90
82
  },
91
83
  production: {
92
- baseUrl: "https://api.symbols.app",
93
84
  socketUrl: "https://api.symbols.app",
94
- routerUrl: "https://api.symbols.app",
95
85
  apiUrl: "https://api.symbols.app",
96
86
  basedEnv: "production",
97
87
  basedProject: "platform-v2-sm",
@@ -113,9 +103,7 @@ const getConfig = () => {
113
103
  const envConfig = { ...CONFIG.common, ...CONFIG[env] };
114
104
  const finalConfig = {
115
105
  ...envConfig,
116
- baseUrl: process.env.SYMBOLS_APP_BASE_URL || envConfig.baseUrl,
117
106
  socketUrl: process.env.SYMBOLS_APP_SOCKET_URL || envConfig.socketUrl,
118
- routerUrl: process.env.SYMBOLS_APP_ROUTER_URL || envConfig.routerUrl,
119
107
  apiUrl: process.env.SYMBOLS_APP_API_URL || envConfig.apiUrl,
120
108
  basedEnv: process.env.SYMBOLS_APP_BASED_ENV || envConfig.basedEnv,
121
109
  basedProject: process.env.SYMBOLS_APP_BASED_PROJECT || envConfig.basedProject,
@@ -128,12 +116,8 @@ const getConfig = () => {
128
116
  // Store all environment variables for potential future use
129
117
  };
130
118
  const requiredFields = [
131
- "baseUrl",
132
119
  "socketUrl",
133
120
  "apiUrl",
134
- "basedEnv",
135
- "basedProject",
136
- "basedOrg",
137
121
  "githubClientId",
138
122
  "googleClientId"
139
123
  ];
package/dist/cjs/index.js CHANGED
@@ -28,18 +28,15 @@ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: tru
28
28
  var index_exports = {};
29
29
  __export(index_exports, {
30
30
  SDK: () => SDK,
31
- createAIService: () => import_services3.createAIService,
32
31
  createAuthService: () => import_services3.createAuthService,
32
+ createCollabService: () => import_services3.createCollabService,
33
33
  createCoreService: () => import_services3.createCoreService,
34
- createSocketService: () => import_services3.createSocketService,
35
- createSymstoryService: () => import_services3.createSymstoryService,
36
34
  default: () => index_default,
37
35
  environment: () => import_environment2.default
38
36
  });
39
37
  module.exports = __toCommonJS(index_exports);
40
38
  var import_services = require("./services/index.js");
41
39
  var import_services2 = require("./utils/services.js");
42
- var import_SymstoryService = require("./services/SymstoryService.js");
43
40
  var import_environment = __toESM(require("./config/environment.js"), 1);
44
41
  var import_services3 = require("./services/index.js");
45
42
  var import_environment2 = __toESM(require("./config/environment.js"), 1);
@@ -65,29 +62,15 @@ class SDK {
65
62
  })
66
63
  ),
67
64
  this._initService(
68
- "socket",
69
- (0, import_services.createSocketService)({
70
- context: this._context,
71
- options: this._options
72
- })
73
- ),
74
- this._initService(
75
- "symstory",
76
- (0, import_services.createSymstoryService)({
77
- context: this._context,
78
- options: this._options
79
- })
80
- ),
81
- this._initService(
82
- "ai",
83
- (0, import_services.createAIService)({
65
+ "core",
66
+ (0, import_services.createCoreService)({
84
67
  context: this._context,
85
68
  options: this._options
86
69
  })
87
70
  ),
88
71
  this._initService(
89
- "core",
90
- (0, import_services.createCoreService)({
72
+ "collab",
73
+ (0, import_services.createCollabService)({
91
74
  context: this._context,
92
75
  options: this._options
93
76
  })
@@ -111,7 +94,7 @@ class SDK {
111
94
  const defaults = {
112
95
  useNewServices: true,
113
96
  // Use new service implementations by default
114
- baseUrl: import_environment.default.baseUrl,
97
+ apiUrl: import_environment.default.apiUrl,
115
98
  socketUrl: import_environment.default.socketUrl,
116
99
  timeout: 3e4,
117
100
  retryAttempts: 3,
@@ -134,9 +117,6 @@ class SDK {
134
117
  };
135
118
  for (const service of this._services.values()) {
136
119
  service.updateContext(this._context);
137
- if (service instanceof import_SymstoryService.SymstoryService) {
138
- service.init();
139
- }
140
120
  }
141
121
  }
142
122
  // Check if SDK is ready
@@ -27,7 +27,7 @@ class AIService extends import_BaseService.BaseService {
27
27
  this._client = null;
28
28
  this._initialized = false;
29
29
  this._defaultConfig = {
30
- baseUrl: "https://api.openai.com/v1/engines/text-curie/completions",
30
+ apiUrl: "https://api.openai.com/v1/engines/text-curie/completions",
31
31
  temperature: 0,
32
32
  maxTokens: 2e3,
33
33
  headers: {
@@ -71,7 +71,7 @@ class AIService extends import_BaseService.BaseService {
71
71
  };
72
72
  }
73
73
  validateConfig(config = {}) {
74
- const requiredFields = ["baseUrl", "temperature", "maxTokens"];
74
+ const requiredFields = ["apiUrl", "temperature", "maxTokens"];
75
75
  const missingFields = requiredFields.filter((field) => !config[field]);
76
76
  if (missingFields.length > 0) {
77
77
  throw new Error(
@@ -99,7 +99,7 @@ class AIService extends import_BaseService.BaseService {
99
99
  ...opts
100
100
  })
101
101
  };
102
- const response = await this._request(config.baseUrl, options);
102
+ const response = await this._request(config.apiUrl, options);
103
103
  return response;
104
104
  } catch (error) {
105
105
  throw new Error(`AI prompt failed: ${error.message}`);
@@ -0,0 +1,420 @@
1
+ var __defProp = Object.defineProperty;
2
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
3
+ var __getOwnPropNames = Object.getOwnPropertyNames;
4
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
5
+ var __export = (target, all) => {
6
+ for (var name in all)
7
+ __defProp(target, name, { get: all[name], enumerable: true });
8
+ };
9
+ var __copyProps = (to, from, except, desc) => {
10
+ if (from && typeof from === "object" || typeof from === "function") {
11
+ for (let key of __getOwnPropNames(from))
12
+ if (!__hasOwnProp.call(to, key) && key !== except)
13
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
14
+ }
15
+ return to;
16
+ };
17
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
18
+ var CollabService_exports = {};
19
+ __export(CollabService_exports, {
20
+ CollabService: () => CollabService
21
+ });
22
+ module.exports = __toCommonJS(CollabService_exports);
23
+ var import_BaseService = require("./BaseService.js");
24
+ var import_CollabClient = require("../utils/CollabClient.js");
25
+ var import_RootStateManager = require("../state/RootStateManager.js");
26
+ var import_rootEventBus = require("../state/rootEventBus.js");
27
+ class CollabService extends import_BaseService.BaseService {
28
+ constructor(config) {
29
+ super(config);
30
+ this._client = null;
31
+ this._stateManager = null;
32
+ this._connected = false;
33
+ this._undoStack = [];
34
+ this._redoStack = [];
35
+ this._isUndoRedo = false;
36
+ this._pendingOps = [];
37
+ }
38
+ init({ context }) {
39
+ if (context == null ? void 0 : context.state) {
40
+ try {
41
+ this._stateManager = new import_RootStateManager.RootStateManager(context.state);
42
+ } catch (err) {
43
+ this._setError(err);
44
+ throw err;
45
+ }
46
+ }
47
+ this._setReady();
48
+ }
49
+ /**
50
+ * Overridden to re-initialise the state manager once the root state becomes
51
+ * available via a subsequent SDK `updateContext()` call.
52
+ */
53
+ updateContext(context = {}) {
54
+ super.updateContext(context);
55
+ if (context.state) {
56
+ this._stateManager = new import_RootStateManager.RootStateManager(context.state);
57
+ }
58
+ }
59
+ /**
60
+ * Ensure that the state manager exists. This is called right before any
61
+ * operation that requires access to the root state (e.g. `connect()`).
62
+ * Throws an explicit error if the root state is still missing so that the
63
+ * caller can react accordingly.
64
+ */
65
+ _ensureStateManager() {
66
+ var _a;
67
+ if (!this._stateManager) {
68
+ if (!((_a = this._context) == null ? void 0 : _a.state)) {
69
+ throw new Error("[CollabService] Cannot operate without root state");
70
+ }
71
+ this._stateManager = new import_RootStateManager.RootStateManager(this._context.state);
72
+ }
73
+ }
74
+ /* ---------- Connection Management ---------- */
75
+ async connect(options = {}) {
76
+ var _a, _b, _c, _d, _e;
77
+ this._ensureStateManager();
78
+ const {
79
+ authToken: jwt,
80
+ projectId,
81
+ branch = "main",
82
+ pro
83
+ } = {
84
+ ...this._context,
85
+ ...options
86
+ };
87
+ console.log(jwt, projectId, branch, pro);
88
+ if (!projectId) {
89
+ throw new Error("projectId is required for CollabService connection");
90
+ }
91
+ if (this._client) {
92
+ await this.disconnect();
93
+ }
94
+ try {
95
+ this._client = new import_CollabClient.CollabClient({
96
+ jwt,
97
+ projectId,
98
+ branch,
99
+ live: Boolean(pro)
100
+ });
101
+ await new Promise((resolve) => {
102
+ var _a2, _b2;
103
+ if ((_a2 = this._client.socket) == null ? void 0 : _a2.connected) {
104
+ resolve();
105
+ } else {
106
+ (_b2 = this._client.socket) == null ? void 0 : _b2.once("connect", resolve);
107
+ }
108
+ });
109
+ console.log("[CollabService] socket connected");
110
+ (_a = this._client.socket) == null ? void 0 : _a.on("ops", ({ changes }) => {
111
+ console.log(`ops event`);
112
+ console.log(changes);
113
+ this._stateManager.applyChanges(changes, { fromSocket: true });
114
+ });
115
+ (_b = this._client.socket) == null ? void 0 : _b.on("commit", ({ version }) => {
116
+ this._stateManager.setVersion(version);
117
+ import_rootEventBus.rootBus.emit("checkpoint:done", { version, origin: "auto" });
118
+ });
119
+ (_c = this._client.socket) == null ? void 0 : _c.on("clients", this._handleClientsEvent.bind(this));
120
+ (_d = this._client.socket) == null ? void 0 : _d.on("presence", this._handleClientsEvent.bind(this));
121
+ (_e = this._client.socket) == null ? void 0 : _e.on("cursor", this._handleCursorEvent.bind(this));
122
+ if (this._pendingOps.length) {
123
+ console.log(
124
+ `[CollabService] Flushing ${this._pendingOps.length} offline operation batch(es)`
125
+ );
126
+ this._pendingOps.forEach(({ tuples }) => {
127
+ this.socket.emit("ops", { changes: tuples, ts: Date.now() });
128
+ });
129
+ this._pendingOps.length = 0;
130
+ }
131
+ this._connected = true;
132
+ console.log("[CollabService] Connected to project:", projectId);
133
+ } catch (err) {
134
+ console.error("[CollabService] Connection failed:", err);
135
+ throw err;
136
+ }
137
+ }
138
+ disconnect() {
139
+ var _a;
140
+ if ((_a = this._client) == null ? void 0 : _a.socket) {
141
+ this._client.socket.disconnect();
142
+ }
143
+ this._client = null;
144
+ this._connected = false;
145
+ console.log("[CollabService] Disconnected");
146
+ }
147
+ isConnected() {
148
+ var _a, _b;
149
+ return this._connected && ((_b = (_a = this._client) == null ? void 0 : _a.socket) == null ? void 0 : _b.connected);
150
+ }
151
+ /* convenient shortcuts */
152
+ get ydoc() {
153
+ var _a;
154
+ return (_a = this._client) == null ? void 0 : _a.ydoc;
155
+ }
156
+ get socket() {
157
+ var _a;
158
+ return (_a = this._client) == null ? void 0 : _a.socket;
159
+ }
160
+ toggleLive(f) {
161
+ var _a;
162
+ (_a = this._client) == null ? void 0 : _a.toggleLive(f);
163
+ }
164
+ sendCursor(d) {
165
+ var _a;
166
+ (_a = this._client) == null ? void 0 : _a.sendCursor(d);
167
+ }
168
+ sendPresence(d) {
169
+ var _a;
170
+ (_a = this._client) == null ? void 0 : _a.sendPresence(d);
171
+ }
172
+ /* ---------- data helpers ---------- */
173
+ updateData(tuples, options = {}) {
174
+ var _a;
175
+ this._ensureStateManager();
176
+ const { isUndo = false, isRedo = false } = options;
177
+ if (!isUndo && !isRedo && !this._isUndoRedo) {
178
+ this._trackForUndo(tuples, options);
179
+ }
180
+ this._stateManager.applyChanges(tuples, { ...options });
181
+ if (!this.isConnected()) {
182
+ console.warn("[CollabService] Not connected, queuing real-time update");
183
+ this._pendingOps.push({ tuples, options });
184
+ return;
185
+ }
186
+ if ((_a = this.socket) == null ? void 0 : _a.connected) {
187
+ this.socket.emit("ops", { changes: tuples, ts: Date.now() });
188
+ }
189
+ return { success: true };
190
+ }
191
+ _trackForUndo(tuples, options) {
192
+ var _a;
193
+ const undoOperations = tuples.map((tuple) => {
194
+ const [action, path] = tuple;
195
+ const currentValue = this._getValueAtPath(path);
196
+ if (action === "delete") {
197
+ return ["update", path, currentValue];
198
+ }
199
+ if (typeof currentValue !== "undefined") {
200
+ return ["update", path, currentValue];
201
+ }
202
+ return ["delete", path];
203
+ });
204
+ this._undoStack.push({
205
+ operations: undoOperations,
206
+ originalOperations: tuples,
207
+ options,
208
+ timestamp: Date.now()
209
+ });
210
+ this._redoStack.length = 0;
211
+ const maxUndoSteps = ((_a = this._options) == null ? void 0 : _a.maxUndoSteps) || 50;
212
+ if (this._undoStack.length > maxUndoSteps) {
213
+ this._undoStack.shift();
214
+ }
215
+ }
216
+ _getValueAtPath(path) {
217
+ var _a;
218
+ const state = (_a = this._stateManager) == null ? void 0 : _a.root;
219
+ if (!state || !state.getByPath) {
220
+ return null;
221
+ }
222
+ try {
223
+ return state.getByPath(path);
224
+ } catch (error) {
225
+ console.warn("[CollabService] Could not get value at path:", path, error);
226
+ return null;
227
+ }
228
+ }
229
+ undo() {
230
+ if (!this._undoStack.length) {
231
+ throw new Error("Nothing to undo");
232
+ }
233
+ if (!this.isConnected()) {
234
+ console.warn("[CollabService] Not connected, cannot undo");
235
+ return;
236
+ }
237
+ const undoItem = this._undoStack.pop();
238
+ const { operations, originalOperations, options } = undoItem;
239
+ this._redoStack.push({
240
+ operations: originalOperations,
241
+ originalOperations: operations,
242
+ options,
243
+ timestamp: Date.now()
244
+ });
245
+ this._isUndoRedo = true;
246
+ try {
247
+ this.updateData(operations, {
248
+ ...options,
249
+ isUndo: true,
250
+ message: `Undo: ${options.message || "operation"}`
251
+ });
252
+ } finally {
253
+ this._isUndoRedo = false;
254
+ }
255
+ return operations;
256
+ }
257
+ redo() {
258
+ if (!this._redoStack.length) {
259
+ throw new Error("Nothing to redo");
260
+ }
261
+ if (!this.isConnected()) {
262
+ console.warn("[CollabService] Not connected, cannot redo");
263
+ return;
264
+ }
265
+ const redoItem = this._redoStack.pop();
266
+ const { operations, originalOperations, options } = redoItem;
267
+ this._undoStack.push({
268
+ operations: originalOperations,
269
+ originalOperations: operations,
270
+ options,
271
+ timestamp: Date.now()
272
+ });
273
+ this._isUndoRedo = true;
274
+ try {
275
+ this.updateData(operations, {
276
+ ...options,
277
+ isRedo: true,
278
+ message: `Redo: ${options.message || "operation"}`
279
+ });
280
+ } finally {
281
+ this._isUndoRedo = false;
282
+ }
283
+ return operations;
284
+ }
285
+ /* ---------- Undo/Redo State ---------- */
286
+ canUndo() {
287
+ return this._undoStack.length > 0;
288
+ }
289
+ canRedo() {
290
+ return this._redoStack.length > 0;
291
+ }
292
+ getUndoStackSize() {
293
+ return this._undoStack.length;
294
+ }
295
+ getRedoStackSize() {
296
+ return this._redoStack.length;
297
+ }
298
+ clearUndoHistory() {
299
+ this._undoStack.length = 0;
300
+ this._redoStack.length = 0;
301
+ }
302
+ addItem(type, data, opts = {}) {
303
+ const { value, ...schema } = data;
304
+ const tuples = [
305
+ ["update", [type, data.key], value],
306
+ ["update", ["schema", type, data.key], schema],
307
+ ...opts.additionalChanges || []
308
+ ];
309
+ return this.updateData(tuples, opts);
310
+ }
311
+ addMultipleItems(items, opts = {}) {
312
+ const tuples = [];
313
+ items.forEach(([type, data]) => {
314
+ const { value, ...schema } = data;
315
+ tuples.push(
316
+ ["update", [type, data.key], value],
317
+ ["update", ["schema", type, data.key], schema]
318
+ );
319
+ });
320
+ this.updateData([...tuples, ...opts.additionalChanges || []], {
321
+ message: `Created ${tuples.length} items`,
322
+ ...opts
323
+ });
324
+ return tuples;
325
+ }
326
+ updateItem(type, data, opts = {}) {
327
+ const { value, ...schema } = data;
328
+ const tuples = [
329
+ ["update", [type, data.key], value],
330
+ ["update", ["schema", type, data.key], schema]
331
+ ];
332
+ return this.updateData(tuples, opts);
333
+ }
334
+ deleteItem(type, key, opts = {}) {
335
+ const tuples = [
336
+ ["delete", [type, key]],
337
+ ["delete", ["schema", type, key]],
338
+ ...opts.additionalChanges || []
339
+ ];
340
+ return this.updateData(tuples, {
341
+ message: `Deleted ${key} from ${type}`,
342
+ ...opts
343
+ });
344
+ }
345
+ /* ---------- socket event helpers ---------- */
346
+ /**
347
+ * Handle "clients" or "presence" events coming from the collab socket.
348
+ * The backend sends the full `clients` object which we directly patch to
349
+ * the root state, mimicking the legacy SocketService behaviour so that
350
+ * existing UI components keep working unmodified.
351
+ */
352
+ _handleClientsEvent(data = {}) {
353
+ var _a;
354
+ const root = (_a = this._stateManager) == null ? void 0 : _a.root;
355
+ if (root && typeof root.replace === "function") {
356
+ root.replace(
357
+ { clients: data },
358
+ {
359
+ fromSocket: true,
360
+ preventUpdate: true
361
+ }
362
+ );
363
+ }
364
+ }
365
+ /**
366
+ * Handle granular cursor updates coming from the socket.
367
+ * Expected payload: { userId, positions?, chosenPos?, ... }
368
+ * Only the provided fields are patched into the state tree.
369
+ */
370
+ _handleCursorEvent(payload = {}) {
371
+ const { userId, positions, chosenPos, ...rest } = payload || {};
372
+ if (!userId) {
373
+ return;
374
+ }
375
+ const tuples = [];
376
+ if (positions) {
377
+ tuples.push([
378
+ "update",
379
+ ["canvas", "clients", userId, "positions"],
380
+ positions
381
+ ]);
382
+ }
383
+ if (chosenPos) {
384
+ tuples.push([
385
+ "update",
386
+ ["canvas", "clients", userId, "chosenPos"],
387
+ chosenPos
388
+ ]);
389
+ }
390
+ if (Object.keys(rest).length) {
391
+ tuples.push(["update", ["canvas", "clients", userId], rest]);
392
+ }
393
+ if (tuples.length) {
394
+ this._stateManager.applyChanges(tuples, { fromSocket: true });
395
+ }
396
+ }
397
+ /* ---------- Manual checkpoint ---------- */
398
+ /**
399
+ * Manually request a checkpoint / commit of buffered operations on the server.
400
+ * Resolves with the new version number once the backend confirms via the
401
+ * regular "commit" event.
402
+ */
403
+ checkpoint() {
404
+ if (!this.isConnected()) {
405
+ console.warn("[CollabService] Not connected, cannot request checkpoint");
406
+ return Promise.reject(new Error("Not connected"));
407
+ }
408
+ return new Promise((resolve) => {
409
+ var _a, _b;
410
+ const handler = ({ version }) => {
411
+ var _a2;
412
+ (_a2 = this.socket) == null ? void 0 : _a2.off("commit", handler);
413
+ import_rootEventBus.rootBus.emit("checkpoint:done", { version, origin: "manual" });
414
+ resolve(version);
415
+ };
416
+ (_a = this.socket) == null ? void 0 : _a.once("commit", handler);
417
+ (_b = this.socket) == null ? void 0 : _b.emit("checkpoint");
418
+ });
419
+ }
420
+ }