@symbo.ls/sdk 2.32.22 → 2.32.26

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 (34) hide show
  1. package/dist/cjs/services/AuthService.js +6 -0
  2. package/dist/cjs/services/BaseService.js +1 -1
  3. package/dist/cjs/services/CollabService.js +24 -6
  4. package/dist/cjs/utils/changePreprocessor.js +58 -2
  5. package/dist/cjs/utils/services.js +1 -0
  6. package/dist/esm/index.js +90 -9
  7. package/dist/esm/services/AdminService.js +1 -1
  8. package/dist/esm/services/AuthService.js +7 -1
  9. package/dist/esm/services/BaseService.js +1 -1
  10. package/dist/esm/services/BranchService.js +1 -1
  11. package/dist/esm/services/CollabService.js +83 -9
  12. package/dist/esm/services/DnsService.js +1 -1
  13. package/dist/esm/services/FileService.js +1 -1
  14. package/dist/esm/services/PaymentService.js +1 -1
  15. package/dist/esm/services/PlanService.js +1 -1
  16. package/dist/esm/services/ProjectService.js +59 -3
  17. package/dist/esm/services/PullRequestService.js +1 -1
  18. package/dist/esm/services/ScreenshotService.js +1 -1
  19. package/dist/esm/services/SubscriptionService.js +1 -1
  20. package/dist/esm/services/TrackingService.js +1 -1
  21. package/dist/esm/services/index.js +89 -9
  22. package/dist/esm/utils/changePreprocessor.js +58 -2
  23. package/dist/esm/utils/services.js +1 -0
  24. package/dist/node/services/AuthService.js +6 -0
  25. package/dist/node/services/BaseService.js +1 -1
  26. package/dist/node/services/CollabService.js +24 -6
  27. package/dist/node/utils/changePreprocessor.js +58 -2
  28. package/dist/node/utils/services.js +1 -0
  29. package/package.json +6 -6
  30. package/src/services/AuthService.js +7 -0
  31. package/src/services/BaseService.js +1 -1
  32. package/src/services/CollabService.js +27 -6
  33. package/src/utils/changePreprocessor.js +76 -2
  34. package/src/utils/services.js +1 -0
@@ -682,7 +682,7 @@ var BaseService = class {
682
682
  }
683
683
  }
684
684
  _requireAuth() {
685
- if (!this._context.authToken) {
685
+ if (!this.getAuthToken()) {
686
686
  throw new Error("Authentication required");
687
687
  }
688
688
  }
@@ -17588,7 +17588,7 @@ var BaseService = class {
17588
17588
  }
17589
17589
  }
17590
17590
  _requireAuth() {
17591
- if (!this._context.authToken) {
17591
+ if (!this.getAuthToken()) {
17592
17592
  throw new Error("Authentication required");
17593
17593
  }
17594
17594
  }
@@ -31640,7 +31640,7 @@ var BaseService = class {
31640
31640
  }
31641
31641
  }
31642
31642
  _requireAuth() {
31643
- if (!this._context.authToken) {
31643
+ if (!this.getAuthToken()) {
31644
31644
  throw new Error("Authentication required");
31645
31645
  }
31646
31646
  }
@@ -32190,6 +32190,12 @@ var AuthService = class extends BaseService {
32190
32190
  throw new Error(`Failed to get user profile: ${error.message}`, { cause: error });
32191
32191
  }
32192
32192
  }
32193
+ getAuthToken() {
32194
+ if (!this._tokenManager) {
32195
+ return null;
32196
+ }
32197
+ return this._tokenManager.getAccessToken();
32198
+ }
32193
32199
  /**
32194
32200
  * Get stored authentication state (backward compatibility method)
32195
32201
  * Replaces AuthService.getStoredAuthState()
@@ -42896,6 +42902,48 @@ function getByPathSafe(root, path) {
42896
42902
  return null;
42897
42903
  }
42898
42904
  }
42905
+ function resolveNextValueFromTuples(tuples, path) {
42906
+ if (!Array.isArray(tuples) || !Array.isArray(path)) {
42907
+ return null;
42908
+ }
42909
+ for (let i3 = tuples.length - 1; i3 >= 0; i3--) {
42910
+ const t4 = tuples[i3];
42911
+ if (!Array.isArray(t4) || t4.length < 3) {
42912
+ continue;
42913
+ }
42914
+ const [action, tuplePath, tupleValue] = t4;
42915
+ if (action !== "update" && action !== "set" || !Array.isArray(tuplePath)) {
42916
+ continue;
42917
+ }
42918
+ if (tuplePath.length > path.length) {
42919
+ continue;
42920
+ }
42921
+ let isPrefix = true;
42922
+ for (let j3 = 0; j3 < tuplePath.length; j3++) {
42923
+ if (tuplePath[j3] !== path[j3]) {
42924
+ isPrefix = false;
42925
+ break;
42926
+ }
42927
+ }
42928
+ if (!isPrefix) {
42929
+ continue;
42930
+ }
42931
+ if (tuplePath.length === path.length) {
42932
+ return tupleValue;
42933
+ }
42934
+ let current2 = tupleValue;
42935
+ for (let j3 = tuplePath.length; j3 < path.length; j3++) {
42936
+ if (current2 == null) {
42937
+ return null;
42938
+ }
42939
+ current2 = current2[path[j3]];
42940
+ }
42941
+ if (current2 !== null) {
42942
+ return current2;
42943
+ }
42944
+ }
42945
+ return null;
42946
+ }
42899
42947
  function preprocessChanges(root, tuples = [], options = {}) {
42900
42948
  const expandTuple = (t4) => {
42901
42949
  const [action, path, value2] = t4 || [];
@@ -42970,7 +43018,21 @@ function preprocessChanges(root, tuples = [], options = {}) {
42970
43018
  return Array.isArray(tuples) ? tuples.slice() : [];
42971
43019
  }
42972
43020
  })();
42973
- const baseOrders = computeOrdersForTuples(root, granularChanges);
43021
+ const hydratedGranularChanges = granularChanges.map((t4) => {
43022
+ if (!Array.isArray(t4) || t4.length < 3) {
43023
+ return t4;
43024
+ }
43025
+ const [action, path] = t4;
43026
+ if (action !== "update" && action !== "set" || !Array.isArray(path)) {
43027
+ return t4;
43028
+ }
43029
+ const nextValue = resolveNextValueFromTuples(tuples, path);
43030
+ if (nextValue === null) {
43031
+ return t4;
43032
+ }
43033
+ return [action, path, nextValue];
43034
+ });
43035
+ const baseOrders = computeOrdersForTuples(root, hydratedGranularChanges);
42974
43036
  const preferOrdersMap = /* @__PURE__ */ new Map();
42975
43037
  for (let i3 = 0; i3 < tuples.length; i3++) {
42976
43038
  const t4 = tuples[i3];
@@ -43000,7 +43062,7 @@ function preprocessChanges(root, tuples = [], options = {}) {
43000
43062
  mergedOrders.push(v3);
43001
43063
  }
43002
43064
  }
43003
- return { granularChanges, orders: mergedOrders };
43065
+ return { granularChanges: hydratedGranularChanges, orders: mergedOrders };
43004
43066
  }
43005
43067
 
43006
43068
  // src/services/CollabService.js
@@ -43220,8 +43282,19 @@ var CollabService = class extends BaseService {
43220
43282
  console.log(
43221
43283
  `[CollabService] Flushing ${this._pendingOps.length} offline operation batch(es)`
43222
43284
  );
43223
- this._pendingOps.forEach(({ changes, granularChanges, orders }) => {
43224
- this.socket.emit("ops", { changes, granularChanges, orders, ts: Date.now() });
43285
+ this._pendingOps.forEach(({ changes, granularChanges, orders, options: opOptions }) => {
43286
+ const { message } = opOptions || {};
43287
+ const ts = Date.now();
43288
+ const payload = {
43289
+ changes,
43290
+ granularChanges,
43291
+ orders,
43292
+ ts
43293
+ };
43294
+ if (message) {
43295
+ payload.message = message;
43296
+ }
43297
+ this.socket.emit("ops", payload);
43225
43298
  });
43226
43299
  this._pendingOps.length = 0;
43227
43300
  }
@@ -43332,6 +43405,7 @@ var CollabService = class extends BaseService {
43332
43405
  tuples,
43333
43406
  Array.isArray(tuples) ? [] : {}
43334
43407
  ) : deepStringifyFunctions(tuples, Array.isArray(tuples) ? [] : {});
43408
+ const { message } = options;
43335
43409
  if (!this.isConnected()) {
43336
43410
  console.warn("[CollabService] Not connected, queuing real-time update");
43337
43411
  this._pendingOps.push({
@@ -43343,18 +43417,24 @@ var CollabService = class extends BaseService {
43343
43417
  return;
43344
43418
  }
43345
43419
  if ((_c = this.socket) == null ? void 0 : _c.connected) {
43420
+ const ts = Date.now();
43346
43421
  console.log("[CollabService] Sending operations to the backend", {
43347
43422
  changes: stringifiedTuples,
43348
43423
  granularChanges: stringifiedGranularTuples,
43349
43424
  orders,
43350
- ts: Date.now()
43425
+ ts,
43426
+ message
43351
43427
  });
43352
- this.socket.emit("ops", {
43428
+ const payload = {
43353
43429
  changes: stringifiedTuples,
43354
43430
  granularChanges: stringifiedGranularTuples,
43355
43431
  orders,
43356
- ts: Date.now()
43357
- });
43432
+ ts
43433
+ };
43434
+ if (message) {
43435
+ payload.message = message;
43436
+ }
43437
+ this.socket.emit("ops", payload);
43358
43438
  }
43359
43439
  return { success: true };
43360
43440
  }
@@ -340,6 +340,48 @@ function getByPathSafe(root, path) {
340
340
  return null;
341
341
  }
342
342
  }
343
+ function resolveNextValueFromTuples(tuples, path) {
344
+ if (!Array.isArray(tuples) || !Array.isArray(path)) {
345
+ return null;
346
+ }
347
+ for (let i = tuples.length - 1; i >= 0; i--) {
348
+ const t = tuples[i];
349
+ if (!Array.isArray(t) || t.length < 3) {
350
+ continue;
351
+ }
352
+ const [action, tuplePath, tupleValue] = t;
353
+ if (action !== "update" && action !== "set" || !Array.isArray(tuplePath)) {
354
+ continue;
355
+ }
356
+ if (tuplePath.length > path.length) {
357
+ continue;
358
+ }
359
+ let isPrefix = true;
360
+ for (let j = 0; j < tuplePath.length; j++) {
361
+ if (tuplePath[j] !== path[j]) {
362
+ isPrefix = false;
363
+ break;
364
+ }
365
+ }
366
+ if (!isPrefix) {
367
+ continue;
368
+ }
369
+ if (tuplePath.length === path.length) {
370
+ return tupleValue;
371
+ }
372
+ let current = tupleValue;
373
+ for (let j = tuplePath.length; j < path.length; j++) {
374
+ if (current == null) {
375
+ return null;
376
+ }
377
+ current = current[path[j]];
378
+ }
379
+ if (current !== null) {
380
+ return current;
381
+ }
382
+ }
383
+ return null;
384
+ }
343
385
  function preprocessChanges(root, tuples = [], options = {}) {
344
386
  const expandTuple = (t) => {
345
387
  const [action, path, value] = t || [];
@@ -414,7 +456,21 @@ function preprocessChanges(root, tuples = [], options = {}) {
414
456
  return Array.isArray(tuples) ? tuples.slice() : [];
415
457
  }
416
458
  })();
417
- const baseOrders = computeOrdersForTuples(root, granularChanges);
459
+ const hydratedGranularChanges = granularChanges.map((t) => {
460
+ if (!Array.isArray(t) || t.length < 3) {
461
+ return t;
462
+ }
463
+ const [action, path] = t;
464
+ if (action !== "update" && action !== "set" || !Array.isArray(path)) {
465
+ return t;
466
+ }
467
+ const nextValue = resolveNextValueFromTuples(tuples, path);
468
+ if (nextValue === null) {
469
+ return t;
470
+ }
471
+ return [action, path, nextValue];
472
+ });
473
+ const baseOrders = computeOrdersForTuples(root, hydratedGranularChanges);
418
474
  const preferOrdersMap = /* @__PURE__ */ new Map();
419
475
  for (let i = 0; i < tuples.length; i++) {
420
476
  const t = tuples[i];
@@ -444,7 +500,7 @@ function preprocessChanges(root, tuples = [], options = {}) {
444
500
  mergedOrders.push(v);
445
501
  }
446
502
  }
447
- return { granularChanges, orders: mergedOrders };
503
+ return { granularChanges: hydratedGranularChanges, orders: mergedOrders };
448
504
  }
449
505
  export {
450
506
  preprocessChanges
@@ -21,6 +21,7 @@ var SERVICE_METHODS = {
21
21
  // Core service methods (new - replaces most based/auth functionality)
22
22
  // Auth methods
23
23
  getStoredAuthState: "auth",
24
+ getAuthToken: "auth",
24
25
  register: "auth",
25
26
  login: "auth",
26
27
  logout: "auth",
@@ -319,6 +319,12 @@ class AuthService extends BaseService {
319
319
  throw new Error(`Failed to get user profile: ${error.message}`, { cause: error });
320
320
  }
321
321
  }
322
+ getAuthToken() {
323
+ if (!this._tokenManager) {
324
+ return null;
325
+ }
326
+ return this._tokenManager.getAccessToken();
327
+ }
322
328
  /**
323
329
  * Get stored authentication state (backward compatibility method)
324
330
  * Replaces AuthService.getStoredAuthState()
@@ -90,7 +90,7 @@ class BaseService {
90
90
  }
91
91
  }
92
92
  _requireAuth() {
93
- if (!this._context.authToken) {
93
+ if (!this.getAuthToken()) {
94
94
  throw new Error("Authentication required");
95
95
  }
96
96
  }
@@ -221,8 +221,19 @@ class CollabService extends BaseService {
221
221
  console.log(
222
222
  `[CollabService] Flushing ${this._pendingOps.length} offline operation batch(es)`
223
223
  );
224
- this._pendingOps.forEach(({ changes, granularChanges, orders }) => {
225
- this.socket.emit("ops", { changes, granularChanges, orders, ts: Date.now() });
224
+ this._pendingOps.forEach(({ changes, granularChanges, orders, options: opOptions }) => {
225
+ const { message } = opOptions || {};
226
+ const ts = Date.now();
227
+ const payload = {
228
+ changes,
229
+ granularChanges,
230
+ orders,
231
+ ts
232
+ };
233
+ if (message) {
234
+ payload.message = message;
235
+ }
236
+ this.socket.emit("ops", payload);
226
237
  });
227
238
  this._pendingOps.length = 0;
228
239
  }
@@ -333,6 +344,7 @@ class CollabService extends BaseService {
333
344
  tuples,
334
345
  Array.isArray(tuples) ? [] : {}
335
346
  ) : deepStringifyFunctions(tuples, Array.isArray(tuples) ? [] : {});
347
+ const { message } = options;
336
348
  if (!this.isConnected()) {
337
349
  console.warn("[CollabService] Not connected, queuing real-time update");
338
350
  this._pendingOps.push({
@@ -344,18 +356,24 @@ class CollabService extends BaseService {
344
356
  return;
345
357
  }
346
358
  if ((_c = this.socket) == null ? void 0 : _c.connected) {
359
+ const ts = Date.now();
347
360
  console.log("[CollabService] Sending operations to the backend", {
348
361
  changes: stringifiedTuples,
349
362
  granularChanges: stringifiedGranularTuples,
350
363
  orders,
351
- ts: Date.now()
364
+ ts,
365
+ message
352
366
  });
353
- this.socket.emit("ops", {
367
+ const payload = {
354
368
  changes: stringifiedTuples,
355
369
  granularChanges: stringifiedGranularTuples,
356
370
  orders,
357
- ts: Date.now()
358
- });
371
+ ts
372
+ };
373
+ if (message) {
374
+ payload.message = message;
375
+ }
376
+ this.socket.emit("ops", payload);
359
377
  }
360
378
  return { success: true };
361
379
  }
@@ -13,6 +13,48 @@ function getByPathSafe(root, path) {
13
13
  return null;
14
14
  }
15
15
  }
16
+ function resolveNextValueFromTuples(tuples, path) {
17
+ if (!Array.isArray(tuples) || !Array.isArray(path)) {
18
+ return null;
19
+ }
20
+ for (let i = tuples.length - 1; i >= 0; i--) {
21
+ const t = tuples[i];
22
+ if (!Array.isArray(t) || t.length < 3) {
23
+ continue;
24
+ }
25
+ const [action, tuplePath, tupleValue] = t;
26
+ if (action !== "update" && action !== "set" || !Array.isArray(tuplePath)) {
27
+ continue;
28
+ }
29
+ if (tuplePath.length > path.length) {
30
+ continue;
31
+ }
32
+ let isPrefix = true;
33
+ for (let j = 0; j < tuplePath.length; j++) {
34
+ if (tuplePath[j] !== path[j]) {
35
+ isPrefix = false;
36
+ break;
37
+ }
38
+ }
39
+ if (!isPrefix) {
40
+ continue;
41
+ }
42
+ if (tuplePath.length === path.length) {
43
+ return tupleValue;
44
+ }
45
+ let current = tupleValue;
46
+ for (let j = tuplePath.length; j < path.length; j++) {
47
+ if (current == null) {
48
+ return null;
49
+ }
50
+ current = current[path[j]];
51
+ }
52
+ if (current !== null) {
53
+ return current;
54
+ }
55
+ }
56
+ return null;
57
+ }
16
58
  function preprocessChanges(root, tuples = [], options = {}) {
17
59
  const expandTuple = (t) => {
18
60
  const [action, path, value] = t || [];
@@ -87,7 +129,21 @@ function preprocessChanges(root, tuples = [], options = {}) {
87
129
  return Array.isArray(tuples) ? tuples.slice() : [];
88
130
  }
89
131
  })();
90
- const baseOrders = computeOrdersForTuples(root, granularChanges);
132
+ const hydratedGranularChanges = granularChanges.map((t) => {
133
+ if (!Array.isArray(t) || t.length < 3) {
134
+ return t;
135
+ }
136
+ const [action, path] = t;
137
+ if (action !== "update" && action !== "set" || !Array.isArray(path)) {
138
+ return t;
139
+ }
140
+ const nextValue = resolveNextValueFromTuples(tuples, path);
141
+ if (nextValue === null) {
142
+ return t;
143
+ }
144
+ return [action, path, nextValue];
145
+ });
146
+ const baseOrders = computeOrdersForTuples(root, hydratedGranularChanges);
91
147
  const preferOrdersMap = /* @__PURE__ */ new Map();
92
148
  for (let i = 0; i < tuples.length; i++) {
93
149
  const t = tuples[i];
@@ -117,7 +173,7 @@ function preprocessChanges(root, tuples = [], options = {}) {
117
173
  mergedOrders.push(v);
118
174
  }
119
175
  }
120
- return { granularChanges, orders: mergedOrders };
176
+ return { granularChanges: hydratedGranularChanges, orders: mergedOrders };
121
177
  }
122
178
  export {
123
179
  preprocessChanges
@@ -20,6 +20,7 @@ const SERVICE_METHODS = {
20
20
  // Core service methods (new - replaces most based/auth functionality)
21
21
  // Auth methods
22
22
  getStoredAuthState: "auth",
23
+ getAuthToken: "auth",
23
24
  register: "auth",
24
25
  login: "auth",
25
26
  logout: "auth",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@symbo.ls/sdk",
3
- "version": "2.32.22",
3
+ "version": "2.32.26",
4
4
  "type": "module",
5
5
  "main": "dist/esm/index.js",
6
6
  "module": "dist/esm/index.js",
@@ -46,12 +46,12 @@
46
46
  "test:user": "cross-env NODE_ENV=$NODE_ENV npx tape integration-tests/index.js integration-tests/user/*.test.js | tap-spec"
47
47
  },
48
48
  "dependencies": {
49
- "@domql/element": "^2.32.22",
50
- "@domql/utils": "^2.32.22",
49
+ "@domql/element": "^2.32.26",
50
+ "@domql/utils": "^2.32.26",
51
51
  "@grafana/faro-web-sdk": "^1.19.0",
52
52
  "@grafana/faro-web-tracing": "^1.19.0",
53
- "@symbo.ls/router": "^2.32.22",
54
- "@symbo.ls/socket": "^2.32.22",
53
+ "@symbo.ls/router": "^2.32.26",
54
+ "@symbo.ls/socket": "^2.32.26",
55
55
  "acorn": "^8.14.0",
56
56
  "acorn-walk": "^8.3.4",
57
57
  "dexie": "^4.0.11",
@@ -73,5 +73,5 @@
73
73
  "tap-spec": "^5.0.0",
74
74
  "tape": "^5.9.0"
75
75
  },
76
- "gitHead": "937da946c8fb840d8282c986c50330584573551a"
76
+ "gitHead": "66d87154b447b4b96fdc5030ab372fbfeb4547f3"
77
77
  }
@@ -370,6 +370,13 @@ export class AuthService extends BaseService {
370
370
  }
371
371
  }
372
372
 
373
+ getAuthToken() {
374
+ if (!this._tokenManager) {
375
+ return null
376
+ }
377
+ return this._tokenManager.getAccessToken()
378
+ }
379
+
373
380
  /**
374
381
  * Get stored authentication state (backward compatibility method)
375
382
  * Replaces AuthService.getStoredAuthState()
@@ -100,7 +100,7 @@ export class BaseService {
100
100
  }
101
101
 
102
102
  _requireAuth () {
103
- if (!this._context.authToken) {
103
+ if (!this.getAuthToken()) {
104
104
  throw new Error('Authentication required')
105
105
  }
106
106
  }
@@ -290,8 +290,19 @@ export class CollabService extends BaseService {
290
290
  console.log(
291
291
  `[CollabService] Flushing ${this._pendingOps.length} offline operation batch(es)`
292
292
  )
293
- this._pendingOps.forEach(({ changes, granularChanges, orders }) => {
294
- this.socket.emit('ops', { changes, granularChanges, orders, ts: Date.now() })
293
+ this._pendingOps.forEach(({ changes, granularChanges, orders, options: opOptions }) => {
294
+ const { message } = opOptions || {}
295
+ const ts = Date.now()
296
+ const payload = {
297
+ changes,
298
+ granularChanges,
299
+ orders,
300
+ ts
301
+ }
302
+ if (message) {
303
+ payload.message = message
304
+ }
305
+ this.socket.emit('ops', payload)
295
306
  })
296
307
  this._pendingOps.length = 0
297
308
  }
@@ -423,6 +434,8 @@ export class CollabService extends BaseService {
423
434
  )
424
435
  : deepStringifyFunctions(tuples, Array.isArray(tuples) ? [] : {})
425
436
 
437
+ const { message } = options
438
+
426
439
  // If not connected yet, queue the operations for later synchronisation.
427
440
  if (!this.isConnected()) {
428
441
  console.warn('[CollabService] Not connected, queuing real-time update')
@@ -437,18 +450,26 @@ export class CollabService extends BaseService {
437
450
 
438
451
  // When connected, send the operations to the backend.
439
452
  if (this.socket?.connected) {
453
+ const ts = Date.now()
440
454
  console.log('[CollabService] Sending operations to the backend', {
441
455
  changes: stringifiedTuples,
442
456
  granularChanges: stringifiedGranularTuples,
443
457
  orders,
444
- ts: Date.now()
458
+ ts,
459
+ message
445
460
  })
446
- this.socket.emit('ops', {
461
+ const payload = {
447
462
  changes: stringifiedTuples,
448
463
  granularChanges: stringifiedGranularTuples,
449
464
  orders,
450
- ts: Date.now()
451
- })
465
+ ts
466
+ }
467
+
468
+ if (message) {
469
+ payload.message = message
470
+ }
471
+
472
+ this.socket.emit('ops', payload)
452
473
  }
453
474
 
454
475
  return { success: true }
@@ -11,6 +11,65 @@ function getByPathSafe (root, path) {
11
11
  try { return root.getByPath(path) } catch { return null }
12
12
  }
13
13
 
14
+ // Given the original high-level tuples and a fully qualified path, resolve the
15
+ // "next" value that the change set intends to write at that path.
16
+ // This walks tuples from last to first so that later changes win, and supports
17
+ // nested paths where the tuple only targets a parent container, e.g.:
18
+ // ['update', ['components', 'CanvasLogoDropdown'], { ... }]
19
+ // for a granular path like:
20
+ // ['components', 'CanvasLogoDropdown', 'ProjectNav', 'ListInDropdown', 'children']
21
+ function resolveNextValueFromTuples (tuples, path) {
22
+ if (!Array.isArray(tuples) || !Array.isArray(path)) { return null }
23
+
24
+ // Walk from the end to honour the latest change
25
+ for (let i = tuples.length - 1; i >= 0; i--) {
26
+ const t = tuples[i]
27
+ if (!Array.isArray(t) || t.length < 3) {
28
+ // eslint-disable-next-line no-continue
29
+ continue
30
+ }
31
+ const [action, tuplePath, tupleValue] = t
32
+ if ((action !== 'update' && action !== 'set') || !Array.isArray(tuplePath)) {
33
+ // eslint-disable-next-line no-continue
34
+ continue
35
+ }
36
+ if (tuplePath.length > path.length) {
37
+ // eslint-disable-next-line no-continue
38
+ continue
39
+ }
40
+
41
+ // Ensure tuplePath is a prefix of the requested path
42
+ let isPrefix = true
43
+ for (let j = 0; j < tuplePath.length; j++) {
44
+ if (tuplePath[j] !== path[j]) {
45
+ isPrefix = false
46
+ break
47
+ }
48
+ }
49
+ if (!isPrefix) {
50
+ // eslint-disable-next-line no-continue
51
+ continue
52
+ }
53
+
54
+ // Direct match: the tuple already targets the exact path
55
+ if (tuplePath.length === path.length) {
56
+ return tupleValue
57
+ }
58
+
59
+ // Nested match: drill into the tuple value using the remaining segments
60
+ let current = tupleValue
61
+ for (let j = tuplePath.length; j < path.length; j++) {
62
+ if (current == null) { return null }
63
+ current = current[path[j]]
64
+ }
65
+ if (current !== null) {
66
+ return current
67
+ }
68
+ }
69
+
70
+ return null
71
+ }
72
+
14
73
  /**
15
74
  * Preprocess broad project changes into granular changes and ordering metadata.
16
75
  * - Expands top-level object updates (e.g. ['update', ['components'], {...}])
@@ -121,8 +180,23 @@ export function preprocessChanges (root, tuples = [], options = {}) {
121
180
  }
122
181
  })()
123
182
 
183
+ const hydratedGranularChanges = granularChanges.map(t => {
184
+ if (!Array.isArray(t) || t.length < 3) { return t }
185
+ const [action, path] = t
186
+ if ((action !== 'update' && action !== 'set') || !Array.isArray(path)) {
187
+ return t
188
+ }
189
+
190
+ const nextValue = resolveNextValueFromTuples(tuples, path)
191
+ if (nextValue === null) {
192
+ return t
193
+ }
194
+
195
+ return [action, path, nextValue]
196
+ })
197
+
124
198
  // Base orders from granular changes/state
125
- const baseOrders = computeOrdersForTuples(root, granularChanges)
199
+ const baseOrders = computeOrdersForTuples(root, hydratedGranularChanges)
126
200
 
127
201
  // Prefer explicit order for containers updated via ['update', [type], value] or ['update', [type, key], value]
128
202
  const preferOrdersMap = new Map()
@@ -161,5 +235,5 @@ export function preprocessChanges (root, tuples = [], options = {}) {
161
235
  if (!seen.has(k)) { seen.add(k); mergedOrders.push(v) }
162
236
  }
163
237
 
164
- return { granularChanges, orders: mergedOrders }
238
+ return { granularChanges: hydratedGranularChanges, orders: mergedOrders }
165
239
  }
@@ -23,6 +23,7 @@ export const SERVICE_METHODS = {
23
23
  // Core service methods (new - replaces most based/auth functionality)
24
24
  // Auth methods
25
25
  getStoredAuthState: 'auth',
26
+ getAuthToken: 'auth',
26
27
  register: 'auth',
27
28
  login: 'auth',
28
29
  logout: 'auth',