@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.
- package/README.md +2 -2
- package/dist/cjs/config/environment.js +5 -21
- package/dist/cjs/index.js +6 -26
- package/dist/cjs/services/AIService.js +3 -3
- package/dist/cjs/services/CollabService.js +420 -0
- package/dist/cjs/services/CoreService.js +651 -107
- package/dist/cjs/services/SocketService.js +207 -59
- package/dist/cjs/services/index.js +5 -13
- package/dist/cjs/state/RootStateManager.js +86 -0
- package/dist/cjs/state/rootEventBus.js +65 -0
- package/dist/cjs/utils/CollabClient.js +157 -0
- package/dist/cjs/utils/TokenManager.js +62 -27
- package/dist/cjs/utils/jsonDiff.js +103 -0
- package/dist/cjs/utils/services.js +129 -88
- package/dist/cjs/utils/symstoryClient.js +5 -5
- package/dist/esm/config/environment.js +5 -21
- package/dist/esm/index.js +20459 -9286
- package/dist/esm/services/AIService.js +3 -3
- package/dist/esm/services/BasedService.js +5 -21
- package/dist/esm/services/CollabService.js +18028 -0
- package/dist/esm/services/CoreService.js +718 -155
- package/dist/esm/services/SocketService.js +323 -58
- package/dist/esm/services/SymstoryService.js +10 -26
- package/dist/esm/services/index.js +20305 -9158
- package/dist/esm/state/RootStateManager.js +102 -0
- package/dist/esm/state/rootEventBus.js +47 -0
- package/dist/esm/utils/CollabClient.js +17483 -0
- package/dist/esm/utils/TokenManager.js +62 -27
- package/dist/esm/utils/jsonDiff.js +6096 -0
- package/dist/esm/utils/services.js +129 -88
- package/dist/esm/utils/symstoryClient.js +10 -26
- package/dist/node/config/environment.js +5 -21
- package/dist/node/index.js +10 -34
- package/dist/node/services/AIService.js +3 -3
- package/dist/node/services/CollabService.js +401 -0
- package/dist/node/services/CoreService.js +651 -107
- package/dist/node/services/SocketService.js +197 -59
- package/dist/node/services/index.js +5 -13
- package/dist/node/state/RootStateManager.js +57 -0
- package/dist/node/state/rootEventBus.js +46 -0
- package/dist/node/utils/CollabClient.js +128 -0
- package/dist/node/utils/TokenManager.js +62 -27
- package/dist/node/utils/jsonDiff.js +74 -0
- package/dist/node/utils/services.js +129 -88
- package/dist/node/utils/symstoryClient.js +5 -5
- package/package.json +12 -6
- package/src/config/environment.js +5 -19
- package/src/index.js +9 -31
- package/src/services/AIService.js +3 -3
- package/src/services/BasedService.js +1 -0
- package/src/services/CollabService.js +491 -0
- package/src/services/CoreService.js +715 -110
- package/src/services/SocketService.js +227 -59
- package/src/services/index.js +6 -13
- package/src/state/RootStateManager.js +71 -0
- package/src/state/rootEventBus.js +48 -0
- package/src/utils/CollabClient.js +161 -0
- package/src/utils/TokenManager.js +68 -30
- package/src/utils/jsonDiff.js +109 -0
- package/src/utils/services.js +140 -88
- package/src/utils/symstoryClient.js +5 -5
- package/dist/cjs/services/SocketIOService.js +0 -307
- package/dist/esm/services/SocketIOService.js +0 -470
- package/dist/node/services/SocketIOService.js +0 -278
- 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
|
-
|
|
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
|
-
|
|
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
|
-
"
|
|
69
|
-
(0, import_services.
|
|
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
|
-
"
|
|
90
|
-
(0, import_services.
|
|
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
|
-
|
|
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
|
-
|
|
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 = ["
|
|
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.
|
|
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
|
+
}
|