roboto-js 3.0.0 → 3.0.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -351,11 +351,47 @@ var RbtObject = /*#__PURE__*/function () {
351
351
  });
352
352
  return clonedObject;
353
353
  }
354
+
355
+ /**
356
+ * Rebuild read_ids/write_ids record meta from iac grant arrays.
357
+ * Keeps denormalized columns in sync with dataJson.iac for delta saves.
358
+ */
359
+ }, {
360
+ key: "_syncIacRecordMeta",
361
+ value: function _syncIacRecordMeta() {
362
+ var iac = _.get(this._data, 'iac');
363
+ if (!iac) return;
364
+ var readIds = new Set();
365
+ var writeIds = new Set();
366
+ if (this.type === '<@iac.user>') {
367
+ writeIds.add(this.id);
368
+ readIds.add(this.id);
369
+ }
370
+ if (iac.creator) {
371
+ writeIds.add(iac.creator);
372
+ readIds.add(iac.creator);
373
+ }
374
+ var collect = function collect(grants, target) {
375
+ if (!grants) return;
376
+ for (var _i = 0, _arr = ['users', 'userGroups', 'organizations', 'userSegments']; _i < _arr.length; _i++) {
377
+ var key = _arr[_i];
378
+ if (Array.isArray(grants[key])) {
379
+ grants[key].forEach(function (id) {
380
+ return target.add(id);
381
+ });
382
+ }
383
+ }
384
+ };
385
+ collect(iac.readGrants, readIds);
386
+ collect(iac.writeGrants, writeIds);
387
+ this._internalData.read_ids = Array.from(readIds).join(',');
388
+ this._internalData.write_ids = Array.from(writeIds).join(',');
389
+ }
354
390
  }, {
355
391
  key: "save",
356
392
  value: function () {
357
393
  var _save = _asyncToGenerator( /*#__PURE__*/_regeneratorRuntime().mark(function _callee3() {
358
- var _response$data, _response$data2, record, response;
394
+ var _response$data, _response$data2, iacGrantChange, record, response;
359
395
  return _regeneratorRuntime().wrap(function _callee3$(_context3) {
360
396
  while (1) switch (_context3.prev = _context3.next) {
361
397
  case 0:
@@ -366,28 +402,34 @@ var RbtObject = /*#__PURE__*/function () {
366
402
  throw new Error('Cannot save object without type');
367
403
  case 2:
368
404
  _context3.prev = 2;
405
+ iacGrantChange = (this.rpcMeta.changes || []).some(function (path) {
406
+ return path.startsWith('iac.readGrants') || path.startsWith('iac.writeGrants');
407
+ });
408
+ if (iacGrantChange) {
409
+ this._syncIacRecordMeta();
410
+ }
369
411
  if (this.rpcMeta.isNew) {
370
412
  record = this.toRecord();
371
413
  } else {
372
414
  record = this.toDeltaRecord();
373
415
  }
374
- _context3.next = 6;
416
+ _context3.next = 8;
375
417
  return this._axios.post('/object_service/saveObject', [record]);
376
- case 6:
418
+ case 8:
377
419
  response = _context3.sent;
378
420
  if (!(response.data.ok === false)) {
379
- _context3.next = 9;
421
+ _context3.next = 11;
380
422
  break;
381
423
  }
382
424
  throw new Error(response.data.message);
383
- case 9:
425
+ case 11:
384
426
  if (!(((_response$data = response.data) === null || _response$data === void 0 ? void 0 : _response$data.result) == 'NO_CHANGES')) {
385
- _context3.next = 12;
427
+ _context3.next = 14;
386
428
  break;
387
429
  }
388
430
  this.rpcMeta.isNew = false;
389
431
  return _context3.abrupt("return", this);
390
- case 12:
432
+ case 14:
391
433
  if ((_response$data2 = response.data) !== null && _response$data2 !== void 0 && _response$data2.newData) {
392
434
  // apply new incoming data
393
435
  this.setData(response.newData);
@@ -397,17 +439,17 @@ var RbtObject = /*#__PURE__*/function () {
397
439
  this.type = response.data.type;
398
440
  this.rpcMeta.isNew = false;
399
441
  return _context3.abrupt("return", this);
400
- case 20:
401
- _context3.prev = 20;
442
+ case 22:
443
+ _context3.prev = 22;
402
444
  _context3.t0 = _context3["catch"](2);
403
445
  this._error = _context3.t0;
404
446
  //console.log(e.response.data);
405
447
  throw _context3.t0;
406
- case 24:
448
+ case 26:
407
449
  case "end":
408
450
  return _context3.stop();
409
451
  }
410
- }, _callee3, this, [[2, 20]]);
452
+ }, _callee3, this, [[2, 22]]);
411
453
  }));
412
454
  function save() {
413
455
  return _save.apply(this, arguments);
@@ -524,19 +566,20 @@ var RbtObject = /*#__PURE__*/function () {
524
566
  this.set(groupsPath, _toConsumableArray(new Set([].concat(_toConsumableArray(existingGroups), _toConsumableArray(groupIds)))));
525
567
  }
526
568
  }
569
+ this._syncIacRecordMeta();
527
570
 
528
571
  // Save if requested
529
572
  if (!save) {
530
- _context4.next = 15;
573
+ _context4.next = 16;
531
574
  break;
532
575
  }
533
- _context4.next = 14;
576
+ _context4.next = 15;
534
577
  return this.save();
535
- case 14:
536
- return _context4.abrupt("return", _context4.sent);
537
578
  case 15:
538
- return _context4.abrupt("return", this);
579
+ return _context4.abrupt("return", _context4.sent);
539
580
  case 16:
581
+ return _context4.abrupt("return", this);
582
+ case 17:
540
583
  case "end":
541
584
  return _context4.stop();
542
585
  }
@@ -769,19 +812,20 @@ var RbtObject = /*#__PURE__*/function () {
769
812
  return !groupIds.includes(id);
770
813
  }));
771
814
  }
815
+ this._syncIacRecordMeta();
772
816
 
773
817
  // Save if requested
774
818
  if (!save) {
775
- _context7.next = 15;
819
+ _context7.next = 16;
776
820
  break;
777
821
  }
778
- _context7.next = 14;
822
+ _context7.next = 15;
779
823
  return this.save();
780
- case 14:
781
- return _context7.abrupt("return", _context7.sent);
782
824
  case 15:
783
- return _context7.abrupt("return", this);
825
+ return _context7.abrupt("return", _context7.sent);
784
826
  case 16:
827
+ return _context7.abrupt("return", this);
828
+ case 17:
785
829
  case "end":
786
830
  return _context7.stop();
787
831
  }
@@ -1,4 +1,4 @@
1
1
  // Auto-generated version file
2
2
  // DO NOT EDIT - This file is automatically updated from package.json
3
- // Version: 3.0.0
4
- export var version = '3.0.0';
3
+ // Version: 3.0.2
4
+ export var version = '3.0.2';
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "roboto-js",
3
- "version": "3.0.0",
3
+ "version": "3.0.2",
4
4
  "type": "module",
5
5
  "description": "",
6
6
  "main": "dist/cjs/index.cjs",
package/src/index.js CHANGED
@@ -298,10 +298,18 @@ export default class Roboto{
298
298
  async confirmUserEmail(params){
299
299
  return this.api.confirmUserEmail(params);
300
300
  }
301
-
302
- //
303
- // Organization management
304
- //
301
+ async sendResetEmailViaFlows(email) {
302
+ return this.api.sendResetEmailViaFlows(email);
303
+ }
304
+ async sendRegistrationVerificationEmail(email) {
305
+ return this.api.sendRegistrationVerificationEmail(email);
306
+ }
307
+ async resendRegistrationCode(email) {
308
+ return this.api.resendRegistrationCode(email);
309
+ }
310
+ async completePasswordReset(params) {
311
+ return this.api.completePasswordReset(params);
312
+ }
305
313
  async loadCurrentOrganization(forceReload = false){
306
314
  return this.api.loadCurrentOrganization(forceReload);
307
315
  }
@@ -315,7 +323,7 @@ export default class Roboto{
315
323
  return this.api.getCurrentOrganization();
316
324
  }
317
325
  get currentOrganization(){
318
- return this.api.currentOrganization;
326
+ return this.api.getCurrentOrganization();
319
327
  }
320
328
 
321
329
  //
package/src/rbt_api.js CHANGED
@@ -1,11 +1,36 @@
1
1
  import axios from 'axios';
2
- import CryptoJS from 'crypto-js';
3
2
  import RbtObject from './rbt_object.js';
4
3
  import RbtUser from './rbt_user.js';
5
4
  import RbtFile from './rbt_file.js';
6
5
  import _ from 'lodash';
7
6
  import { openDB } from 'idb';
8
7
 
8
+ /** IAC working org preference lives under module-scoped settings (`mod.iac`). Legacy reads still honor `mod.currentOrgId`. */
9
+ const USER_MOD_CURRENT_ORG_IAC = 'mod.iac.currentOrgId';
10
+ const USER_MOD_CURRENT_ORG_LEGACY = 'mod.currentOrgId';
11
+
12
+ /** @param {import('./rbt_user.js').default|null|undefined} user */
13
+ function readUserCurrentOrgPreference(user) {
14
+ if (!user || typeof user.get !== 'function') return '';
15
+
16
+ let v = user.get(USER_MOD_CURRENT_ORG_IAC);
17
+ if (v != null && String(v).trim() !== '') return String(v).trim();
18
+ v = user.get(USER_MOD_CURRENT_ORG_LEGACY);
19
+ if (v != null && String(v).trim() !== '') return String(v).trim();
20
+
21
+ const d = typeof user.getData === 'function' ? user.getData() : {};
22
+ v = _.get(d, 'mod.iac.currentOrgId') ?? _.get(d, 'mod.currentOrgId');
23
+ if (v != null && String(v).trim() !== '') return String(v).trim();
24
+
25
+ return '';
26
+ }
27
+
28
+ /** @param {import('./rbt_user.js').default} user */
29
+ function persistUserCurrentOrgPreference(user, orgId) {
30
+ if (!user?.set || orgId == null || String(orgId).trim() === '') return;
31
+ user.set(USER_MOD_CURRENT_ORG_IAC, String(orgId).trim());
32
+ }
33
+
9
34
  export default class RbtApi {
10
35
 
11
36
  constructor({ baseUrl, accesskey, authtoken=null, apikey=null, localStorageAdaptor=null, withCredentials=true }) {
@@ -299,12 +324,11 @@ export default class RbtApi {
299
324
 
300
325
  try {
301
326
 
302
- const response = await this.axios.post('/api/iac/loginUser', [{
327
+ const response = await this.axios.post('/api/iac/login', {
303
328
  email: params.email,
304
- password: (params.password)? CryptoJS.MD5(params.password).toString(CryptoJS.enc.Hex): null, // legacy hash password (md5)
305
- passwd: params.password || null, // current password
306
- code: params.code || null // single-use signon code (password resets)
307
- }]);
329
+ password: params.password || null,
330
+ code: params.code || null,
331
+ });
308
332
 
309
333
  if (response.data.ok === false) {
310
334
  return this._handleError(response);
@@ -471,24 +495,64 @@ export default class RbtApi {
471
495
  }
472
496
 
473
497
 
474
- async confirmUserEmail(confirmCode){
475
-
476
- let params = { emailConfirmCode: confirmCode };
498
+ /**
499
+ * Confirm email with either legacy composite string or { email, code } (6-digit from mod-iac-flows).
500
+ * @param {string|{ email: string, code: string }|{ emailConfirmCode: string }} confirmCodeOrParams
501
+ */
502
+ async confirmUserEmail(confirmCodeOrParams) {
503
+ let params;
504
+ if (typeof confirmCodeOrParams === 'string') {
505
+ params = { emailConfirmCode: confirmCodeOrParams };
506
+ } else if (confirmCodeOrParams && typeof confirmCodeOrParams === 'object') {
507
+ const { email, code, emailConfirmCode } = confirmCodeOrParams;
508
+ if (email && code != null && String(code).length > 0) {
509
+ params = { email, code: String(code) };
510
+ } else if (emailConfirmCode) {
511
+ params = { emailConfirmCode };
512
+ } else {
513
+ const err = new Error('confirmUserEmail: pass a legacy string, { emailConfirmCode }, or { email, code }');
514
+ return this._handleError(err);
515
+ }
516
+ } else {
517
+ const err = new Error('confirmUserEmail: invalid argument');
518
+ return this._handleError(err);
519
+ }
477
520
 
478
521
  try {
479
-
480
522
  const response = await this.axios.post('/api/iac/confirmUserEmail', [params]);
481
-
482
523
  if (response.data.ok === false) {
483
524
  return this._handleError(response);
484
525
  }
485
-
486
526
  return response.data;
487
-
488
527
  } catch (e) {
489
528
  return this._handleError(e);
490
529
  }
530
+ }
531
+
532
+ /** Password reset email — use mod-iac-flows (not deprecated /api/iac/sendPasswordReset). */
533
+ async sendResetEmailViaFlows(email) {
534
+ const response = await this.axios.post('/api/iac-flows/sendResetEmail', { email });
535
+ return response.data;
536
+ }
537
+
538
+ async sendRegistrationVerificationEmail(email) {
539
+ const response = await this.axios.post('/api/iac-flows/sendRegistrationVerification', { email });
540
+ return response.data;
541
+ }
491
542
 
543
+ async resendRegistrationCode(email) {
544
+ const response = await this.axios.post('/api/iac-flows/resendRegistrationCode', { email });
545
+ return response.data;
546
+ }
547
+
548
+ async completePasswordReset({ email, code, newPassword }) {
549
+ const response = await this.axios.post('/api/iac/completePasswordReset', [
550
+ { email, code, newPassword },
551
+ ]);
552
+ if (response.data?.ok === false) {
553
+ return this._handleError(response);
554
+ }
555
+ return response.data;
492
556
  }
493
557
 
494
558
  async loadCurrentUserExtended(){
@@ -516,55 +580,55 @@ export default class RbtApi {
516
580
  }
517
581
 
518
582
  /**
519
- * Load current organization for the authenticated user
520
- * Organization is determined by:
521
- * 1. User's mod.currentOrgId preference
522
- * 2. First organization in user.organizations array
523
- * 3. null if user has no organizations
524
- *
525
- * @param {boolean} forceReload - Force reload from server even if cached
583
+ * Load current organization for the authenticated user.
584
+ *
585
+ * Returns an organization **only when** `user.mod.iac.currentOrgId` is set (explicit selection via
586
+ * `switchOrganization` / `selectCurrentOrganization`; legacy reads also accept `mod.currentOrgId`).
587
+ * There is no fallback to “first org”; callers must guide the user to select an org first.
588
+ *
589
+ * @param {boolean} forceReload - Force reload from server even if cached ID matches preference
526
590
  * @returns {Promise<RbtObject|null>} Organization object or null
527
591
  */
528
592
  async loadCurrentOrganization(forceReload = false) {
529
593
  try {
530
- // Return cached if available and not forcing reload
531
- if (this.currentOrganization && !forceReload) {
532
- //console.log('[RbtApi] Returning cached currentOrganization:', this.currentOrganization.get('name'));
533
- return this.currentOrganization;
534
- }
535
-
536
594
  // Prevent duplicate concurrent loads
537
595
  if (this._loadCurrentOrgPromise) {
538
- //console.log('[RbtApi] Organization load already in progress');
539
596
  return this._loadCurrentOrgPromise;
540
597
  }
541
598
 
542
599
  this._loadCurrentOrgPromise = (async () => {
543
600
  try {
544
- // Ensure user is loaded first
545
601
  const user = await this.loadCurrentUser();
546
602
  if (!user) {
547
- //console.log('[RbtApi] No current user, cannot load organization');
603
+ this.currentOrganization = null;
548
604
  return null;
549
605
  }
550
606
 
551
- // Get organization ID from user preferences or first org
552
- const userData = user.getData();
553
- // organizations array format: [{ id: 'org123', roles: ['owner'] }, ...]
554
- const firstOrg = userData.organizations?.[0];
555
- const orgId = userData.mod?.currentOrgId || (typeof firstOrg === 'object' ? firstOrg?.id : firstOrg);
556
-
607
+ const orgId = readUserCurrentOrgPreference(user);
608
+
557
609
  if (!orgId) {
558
- //console.log('[RbtApi] User has no organizations');
610
+ //console.log('[RbtApi] No current organization selected (mod.iac.currentOrgId unset)');
611
+ this.currentOrganization = null;
612
+ if (this.localStorageAdaptor) {
613
+ try {
614
+ await this.localStorageAdaptor.removeItem('currentOrgId');
615
+ } catch (_) {
616
+ /* noop */
617
+ }
618
+ }
559
619
  return null;
560
620
  }
561
-
562
- console.log('[RbtApi] Resolved organization ID:', orgId, 'from:', {
563
- modCurrentOrgId: userData.mod?.currentOrgId,
564
- firstOrg: firstOrg
565
- });
566
621
 
567
- // Check if we have a cached org ID and it matches
622
+ if (
623
+ !forceReload &&
624
+ this.currentOrganization &&
625
+ String(this.currentOrganization.id) === orgId
626
+ ) {
627
+ return this.currentOrganization;
628
+ }
629
+
630
+ console.log('[RbtApi] Resolved organization ID:', orgId);
631
+
568
632
  if (this.localStorageAdaptor) {
569
633
  const cachedOrgId = await this.localStorageAdaptor.getItem('currentOrgId');
570
634
  if (cachedOrgId && cachedOrgId !== orgId) {
@@ -572,20 +636,16 @@ export default class RbtApi {
572
636
  }
573
637
  }
574
638
 
575
- // Load the organization object
576
- //console.log('[RbtApi] Loading organization:', orgId);
577
- const org = await this.get('<@iac.organization>', orgId);
578
-
639
+ const org = await this.load('<@iac.organization>', orgId);
640
+
579
641
  if (org) {
580
- this.currentOrganization = org; // Already an RbtObject
581
- //console.log('[RbtApi] Current organization loaded:', org.get('name'));
582
-
583
- // Cache in storage if available
642
+ this.currentOrganization = org;
584
643
  if (this.localStorageAdaptor) {
585
644
  await this.localStorageAdaptor.setItem('currentOrgId', orgId);
586
645
  }
587
646
  } else {
588
647
  console.warn('[RbtApi] Organization not found or not accessible:', orgId);
648
+ this.currentOrganization = null;
589
649
  }
590
650
 
591
651
  return this.currentOrganization;
@@ -621,7 +681,7 @@ export default class RbtApi {
621
681
  //console.log('[RbtApi] Switching to organization:', orgId);
622
682
 
623
683
  // Load the new organization
624
- const org = await this.get('<@iac.organization>', orgId);
684
+ const org = await this.load('<@iac.organization>', orgId);
625
685
  if (!org) {
626
686
  throw new Error(`Organization ${orgId} not found or not accessible`);
627
687
  }
@@ -631,7 +691,7 @@ export default class RbtApi {
631
691
  // Save preference to user
632
692
  try {
633
693
  const userObj = await this.loadUser(this.currentUser.id);
634
- userObj.set('mod.currentOrgId', orgId);
694
+ persistUserCurrentOrgPreference(userObj, orgId);
635
695
  await userObj.save();
636
696
 
637
697
  // Update cache
@@ -652,13 +712,17 @@ export default class RbtApi {
652
712
  }
653
713
 
654
714
  /**
655
- * Get current organization (null-safe)
656
- * Returns cached organization without triggering a load
657
- *
658
- * @returns {RbtObject|null} Current organization or null
715
+ * Get cached current organization (sync, null-safe).
716
+ * Returns **null** unless `mod.iac.currentOrgId` (or legacy `mod.currentOrgId`) matches `currentOrganization`.
659
717
  */
660
718
  getCurrentOrganization() {
661
- return this.currentOrganization;
719
+ const org = this.currentOrganization;
720
+ if (!org) return null;
721
+ const user = this.currentUser;
722
+ const selected = readUserCurrentOrgPreference(user);
723
+ if (!selected) return null;
724
+ if (String(org.id) !== selected) return null;
725
+ return org;
662
726
  }
663
727
 
664
728
  /**
@@ -683,8 +747,8 @@ export default class RbtApi {
683
747
 
684
748
  //console.log('[RbtApi] Selecting organization:', orgId);
685
749
 
686
- // Load the organization to verify access
687
- const org = await this.get('<@iac.organization>', orgId);
750
+ // Load the organization via object load (not HTTP `get` — passing orgId as params breaks axios).
751
+ const org = await this.load('<@iac.organization>', orgId);
688
752
  if (!org) {
689
753
  throw new Error(`Organization ${orgId} not found or not accessible`);
690
754
  }
@@ -709,11 +773,9 @@ export default class RbtApi {
709
773
  //console.log('[RbtApi] Organization already in user organizations array');
710
774
  }
711
775
 
712
- // Set as current organization preference - handle both nested path formats
713
- const modData = user.get('mod') || {};
714
- modData.currentOrgId = orgId;
715
- user.set('mod', modData);
716
-
776
+ // Set current organization preference under mod.iac (module-namespaced settings)
777
+ persistUserCurrentOrgPreference(user, orgId);
778
+
717
779
  // Save user record
718
780
  //console.log('[RbtApi] Saving user with organization:', orgId);
719
781
  await user.save();
@@ -956,6 +1018,7 @@ export default class RbtApi {
956
1018
  * - excludeAttrs: An array of attribute paths to exclude from the response (e.g., ['details.log', 'internalData']).
957
1019
  * Excludes the specified paths and all their subpaths.
958
1020
  * - resolveReferences: An array of attribute names whose references should be resolved in the returned objects.
1021
+ * - resolveAttrs: Attribute paths whose mod / reference values should be resolved (e.g., ['configs.mod.guide.completion']).
959
1022
  * - timeout: A numerical value in milliseconds to set a maximum time limit for the query execution.
960
1023
  * - cache: Boolean. When true, enables a 10-second response cache for identical queries. Default: false (no caching).
961
1024
  *
@@ -967,6 +1030,7 @@ export default class RbtApi {
967
1030
  * requestAttrs: ['id', 'configs.title'],
968
1031
  * excludeAttrs: ['details.log'],
969
1032
  * resolveReferences: ['translatableContent'],
1033
+ * resolveAttrs: ['configs.mod.guide.completion'],
970
1034
  * cache: true // opt-in to 10s response caching
971
1035
  * });
972
1036
  *
@@ -980,7 +1044,7 @@ export default class RbtApi {
980
1044
  //console.log('RBTAPI.query INIT', type, params);
981
1045
 
982
1046
  // Validate parameters - reject invalid parameter names
983
- const validParams = ['type', 'where', 'orderBy', 'limit', 'resolveReferences', 'requestAttrs', 'excludeAttrs', 'timeout', 'enableRealtime', 'cache'];
1047
+ const validParams = ['type', 'where', 'orderBy', 'limit', 'resolveReferences', 'resolveAttrs', 'requestAttrs', 'excludeAttrs', 'timeout', 'enableRealtime', 'cache'];
984
1048
  const invalidParams = Object.keys(params).filter(key => !validParams.includes(key));
985
1049
  if (invalidParams.length > 0) {
986
1050
  throw new Error(`Invalid query parameter(s): ${invalidParams.join(', ')}. Valid parameters are: ${validParams.join(', ')}`);
package/src/rbt_object.js CHANGED
@@ -259,12 +259,54 @@ export default class RbtObject{
259
259
  return clonedObject;
260
260
  }
261
261
 
262
+ /**
263
+ * Rebuild read_ids/write_ids record meta from iac grant arrays.
264
+ * Keeps denormalized columns in sync with dataJson.iac for delta saves.
265
+ */
266
+ _syncIacRecordMeta() {
267
+ const iac = _.get(this._data, 'iac');
268
+ if (!iac) return;
269
+
270
+ const readIds = new Set();
271
+ const writeIds = new Set();
272
+
273
+ if (this.type === '<@iac.user>') {
274
+ writeIds.add(this.id);
275
+ readIds.add(this.id);
276
+ }
277
+
278
+ if (iac.creator) {
279
+ writeIds.add(iac.creator);
280
+ readIds.add(iac.creator);
281
+ }
282
+
283
+ const collect = (grants, target) => {
284
+ if (!grants) return;
285
+ for (const key of ['users', 'userGroups', 'organizations', 'userSegments']) {
286
+ if (Array.isArray(grants[key])) {
287
+ grants[key].forEach((id) => target.add(id));
288
+ }
289
+ }
290
+ };
291
+ collect(iac.readGrants, readIds);
292
+ collect(iac.writeGrants, writeIds);
293
+
294
+ this._internalData.read_ids = Array.from(readIds).join(',');
295
+ this._internalData.write_ids = Array.from(writeIds).join(',');
296
+ }
297
+
262
298
  async save() {
263
299
  if (!this._internalData.type) {
264
300
  throw new Error('Cannot save object without type');
265
301
  }
266
302
 
267
303
  try {
304
+ const iacGrantChange = (this.rpcMeta.changes || []).some(
305
+ (path) => path.startsWith('iac.readGrants') || path.startsWith('iac.writeGrants')
306
+ );
307
+ if (iacGrantChange) {
308
+ this._syncIacRecordMeta();
309
+ }
268
310
 
269
311
  let record;
270
312
  if(this.rpcMeta.isNew){
@@ -399,6 +441,8 @@ export default class RbtObject{
399
441
  }
400
442
  }
401
443
 
444
+ this._syncIacRecordMeta();
445
+
402
446
  // Save if requested
403
447
  if (save) {
404
448
  return await this.save();
@@ -558,6 +602,8 @@ export default class RbtObject{
558
602
  this.set(groupsPath, existingGroups.filter(id => !groupIds.includes(id)));
559
603
  }
560
604
 
605
+ this._syncIacRecordMeta();
606
+
561
607
  // Save if requested
562
608
  if (save) {
563
609
  return await this.save();
package/src/version.js CHANGED
@@ -1,4 +1,4 @@
1
1
  // Auto-generated version file
2
2
  // DO NOT EDIT - This file is automatically updated from package.json
3
- // Version: 3.0.0
4
- export const version = '3.0.0';
3
+ // Version: 3.0.2
4
+ export const version = '3.0.2';