@katn30/trakr 1.1.0 → 2.0.0

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.
@@ -27,6 +27,7 @@ __export(index_exports, {
27
27
  TrackedCollectionChanged: () => TrackedCollectionChanged,
28
28
  TrackedObject: () => TrackedObject,
29
29
  Tracker: () => Tracker,
30
+ TrackerSession: () => TrackerSession,
30
31
  TypedEvent: () => TypedEvent
31
32
  });
32
33
  module.exports = __toCommonJS(index_exports);
@@ -249,6 +250,242 @@ function validateSingleProperty(tracked, property) {
249
250
  return error;
250
251
  }
251
252
 
253
+ // src/OperationProperties.ts
254
+ var OperationProperties = class {
255
+ constructor(trackedObject, property, type, validator, coalesceWithin) {
256
+ this.trackedObject = trackedObject;
257
+ this.property = property;
258
+ this.type = type;
259
+ this.validator = validator;
260
+ this.coalesceWithin = coalesceWithin;
261
+ }
262
+ };
263
+
264
+ // src/ExternallyAssigned.ts
265
+ var AUTO_ID = /* @__PURE__ */ Symbol("autoId");
266
+ function AutoId(_target, context) {
267
+ context.addInitializer(function() {
268
+ Object.defineProperty(Object.getPrototypeOf(this), AUTO_ID, {
269
+ value: String(context.name),
270
+ configurable: true
271
+ });
272
+ });
273
+ }
274
+ function getAutoIdProperty(proto) {
275
+ return AUTO_ID in proto ? proto[AUTO_ID] : void 0;
276
+ }
277
+
278
+ // src/TrackedObjectStateMachine.ts
279
+ function applyStateTransition(obj, event, direction, context) {
280
+ if (event === "added") {
281
+ applyAdded(obj, direction);
282
+ } else if (event === "removed") {
283
+ applyRemoved(obj, direction, context);
284
+ } else {
285
+ applyCommitted(obj, direction, context);
286
+ }
287
+ }
288
+ function applyAdded(obj, direction) {
289
+ if (direction === "undo") {
290
+ obj._setState("Unchanged" /* Unchanged */);
291
+ } else {
292
+ obj._setState("Insert" /* Insert */);
293
+ }
294
+ }
295
+ function applyRemoved(obj, direction, context) {
296
+ if (direction === "undo") {
297
+ const prev = context?.prevState ?? "Unchanged" /* Unchanged */;
298
+ obj._setState(prev);
299
+ if (prev === "Insert" /* Insert */) {
300
+ if (context?.prevDirtyCounter !== void 0) {
301
+ obj._setDirtyCounter(context.prevDirtyCounter);
302
+ }
303
+ }
304
+ } else {
305
+ if (obj.state === "Insert" /* Insert */) {
306
+ obj._setState("Unchanged" /* Unchanged */);
307
+ obj._setDirtyCounter(0);
308
+ } else {
309
+ obj._setState("Deleted" /* Deleted */);
310
+ }
311
+ }
312
+ }
313
+ function applyCommitted(obj, direction, context) {
314
+ if (direction === "undo") {
315
+ const prev = context?.prevState ?? "Unchanged" /* Unchanged */;
316
+ if (prev === "Insert" /* Insert */) {
317
+ obj._setState("Deleted" /* Deleted */);
318
+ } else if (prev === "Deleted" /* Deleted */) {
319
+ obj._setState("Insert" /* Insert */);
320
+ } else if (prev === "Changed" /* Changed */) {
321
+ obj._setState("Changed" /* Changed */);
322
+ }
323
+ } else {
324
+ if ((context?.prevState === "Insert" /* Insert */ || context?.prevState === "Changed" /* Changed */) && context.autoIdProp && context.realId !== void 0) {
325
+ obj[context.autoIdProp] = context.realId;
326
+ }
327
+ obj._setState("Unchanged" /* Unchanged */);
328
+ obj._setDirtyCounter(0);
329
+ }
330
+ }
331
+ function buildCommittedContext(obj, autoIdProp, keys) {
332
+ const prevState = obj.state;
333
+ let realId;
334
+ if ((prevState === "Insert" /* Insert */ || prevState === "Changed" /* Changed */) && keys) {
335
+ realId = keys.find((k) => k.trackingId === obj.trackingId)?.value;
336
+ }
337
+ return { prevState, autoIdProp, realId };
338
+ }
339
+
340
+ // src/TrackedObject.ts
341
+ var TrackedObject = class {
342
+ constructor(tracker) {
343
+ this.tracker = tracker;
344
+ this._dirtyCounter = 0;
345
+ this._isValid = true;
346
+ this._state = "Unchanged" /* Unchanged */;
347
+ this.changed = new TypedEvent();
348
+ this.trackedChanged = new TypedEvent();
349
+ if (false) {
350
+ throw new Error(`${this.constructor.name} must be created inside tracker.construct()`);
351
+ }
352
+ this.trackingId = tracker._nextTrackingId();
353
+ this.validationMessages = /* @__PURE__ */ new Map();
354
+ tracker._trackObject(this);
355
+ }
356
+ // ---- StateTarget interface (internal) ----
357
+ /** @internal */
358
+ get state() {
359
+ return this._state;
360
+ }
361
+ /** @internal */
362
+ _setState(value) {
363
+ this._state = value;
364
+ }
365
+ /** @internal */
366
+ _getDirtyCounter() {
367
+ return this._dirtyCounter;
368
+ }
369
+ /** @internal */
370
+ _setDirtyCounter(value) {
371
+ this._dirtyCounter = value;
372
+ }
373
+ // ---- Public API ----
374
+ get validationMessages() {
375
+ return this._validationMessages ?? /* @__PURE__ */ new Map();
376
+ }
377
+ set validationMessages(value) {
378
+ this._validationMessages = value;
379
+ }
380
+ get isValid() {
381
+ return this._isValid;
382
+ }
383
+ set isValid(value) {
384
+ const wasValid = this._isValid;
385
+ this._isValid = value;
386
+ if (wasValid !== value) {
387
+ this.tracker._onValidityChanged(wasValid, value);
388
+ }
389
+ }
390
+ get isDirty() {
391
+ return this._dirtyCounter !== 0;
392
+ }
393
+ get dirtyCounter() {
394
+ return this._dirtyCounter;
395
+ }
396
+ set dirtyCounter(value) {
397
+ this._dirtyCounter = value;
398
+ }
399
+ /** @internal */
400
+ _onCommitted(lastOp, keys) {
401
+ const autoIdProp = getAutoIdProperty(Object.getPrototypeOf(this));
402
+ const context = buildCommittedContext(this, autoIdProp, keys);
403
+ const redoFn = () => applyStateTransition(this, "committed", "do", context);
404
+ const undoFn = () => applyStateTransition(this, "committed", "undo", context);
405
+ if (lastOp) {
406
+ lastOp.updateOrAdd(redoFn, undoFn, new OperationProperties(this, "__state__", 4 /* Object */));
407
+ }
408
+ redoFn();
409
+ }
410
+ /** @internal */
411
+ _markRemoved() {
412
+ const prevState = this._state;
413
+ const prevDirtyCounter = this._dirtyCounter;
414
+ this.tracker._doAndTrack(
415
+ () => applyStateTransition(this, "removed", "do"),
416
+ () => applyStateTransition(this, "removed", "undo", { prevState, prevDirtyCounter }),
417
+ new OperationProperties(this, "__state__", 4 /* Object */)
418
+ );
419
+ }
420
+ /** @internal */
421
+ _markAdded() {
422
+ if (this._state !== "Unchanged" /* Unchanged */) return;
423
+ if (this.tracker._isTrackingSuppressed) return;
424
+ this.tracker._doAndTrack(
425
+ () => applyStateTransition(this, "added", "do"),
426
+ () => applyStateTransition(this, "added", "undo"),
427
+ new OperationProperties(this, "__state__", 4 /* Object */)
428
+ );
429
+ }
430
+ /** @internal */
431
+ _validate(property, errorMessage) {
432
+ if (errorMessage) {
433
+ this.validationMessages.set(property, errorMessage);
434
+ } else {
435
+ this.validationMessages.delete(property);
436
+ }
437
+ this.validationMessages = new Map(this.validationMessages);
438
+ this.isValid = this.validationMessages.size === 0;
439
+ }
440
+ /** @internal */
441
+ _applyValidation(messages) {
442
+ this.validationMessages = messages;
443
+ this.isValid = messages.size === 0;
444
+ }
445
+ destroy() {
446
+ DependencyTracker.clearDeps(this);
447
+ this.tracker._untrackObject(this);
448
+ }
449
+ };
450
+
451
+ // src/TrackerSession.ts
452
+ var TrackerSession = class {
453
+ constructor(scope, _end, _rollback) {
454
+ this._end = _end;
455
+ this._rollback = _rollback;
456
+ this._isDirty = false;
457
+ if (scope && scope.length > 0) {
458
+ this._scope = new Map(scope.map(([obj, props]) => [obj, new Set(props)]));
459
+ }
460
+ }
461
+ /** @internal */
462
+ _onWrite(obj, property) {
463
+ if (this._scope === void 0) return;
464
+ const declaredProps = this._scope.get(obj);
465
+ if (declaredProps === void 0 || !declaredProps.has(property)) return;
466
+ this._isDirty = true;
467
+ }
468
+ get isDirty() {
469
+ if (this._scope === void 0) return void 0;
470
+ return this._isDirty;
471
+ }
472
+ get isValid() {
473
+ if (this._scope === void 0) return void 0;
474
+ for (const [obj, props] of this._scope) {
475
+ for (const prop of props) {
476
+ if (obj.validationMessages.has(prop)) return false;
477
+ }
478
+ }
479
+ return true;
480
+ }
481
+ end() {
482
+ this._end();
483
+ }
484
+ rollback() {
485
+ this._rollback();
486
+ }
487
+ };
488
+
252
489
  // src/Tracker.ts
253
490
  var Tracker = class {
254
491
  constructor() {
@@ -405,6 +642,7 @@ var Tracker = class {
405
642
  this._currentOperationPropertyName = properties.property;
406
643
  if (this.shouldCoalesceChanges(properties)) {
407
644
  this._currentOperation = CollectionUtilities.getLast(this._undoOperations);
645
+ this._version++;
408
646
  this.versionChanged.emit(this._version);
409
647
  } else {
410
648
  this._currentOperation = new Operation();
@@ -421,6 +659,9 @@ var Tracker = class {
421
659
  properties
422
660
  );
423
661
  redoAction();
662
+ if (this._currentSession !== void 0 && properties.property !== void 0 && properties.trackedObject instanceof TrackedObject) {
663
+ this._currentSession._onWrite(properties.trackedObject, properties.property);
664
+ }
424
665
  if (this.isEndingCurrentOperation(properties)) {
425
666
  this._currentOperation = void 0;
426
667
  this._currentOperationOwner = void 0;
@@ -483,13 +724,20 @@ var Tracker = class {
483
724
  this.canRedo = this._redoOperations.length > 0;
484
725
  this.isDirty = CollectionUtilities.getLast(this._undoOperations) !== this._commitStateOperation;
485
726
  }
486
- startComposing() {
487
- if (this._composingBaseIndex !== void 0) return;
727
+ startSession(scope) {
728
+ if (this._currentSession !== void 0) return this._currentSession;
488
729
  this._composingBaseIndex = this._undoOperations.length;
489
730
  this._composingRedoLength = this._redoOperations.length;
731
+ this._currentSession = new TrackerSession(
732
+ scope,
733
+ () => this.endComposing(),
734
+ () => this.rollbackComposing()
735
+ );
736
+ return this._currentSession;
490
737
  }
491
738
  endComposing() {
492
739
  if (this._composingBaseIndex === void 0) return;
740
+ this._currentSession = void 0;
493
741
  const composed = this._undoOperations.splice(this._composingBaseIndex);
494
742
  this._redoOperations.splice(this._composingRedoLength);
495
743
  this._composingBaseIndex = void 0;
@@ -514,6 +762,7 @@ var Tracker = class {
514
762
  }
515
763
  rollbackComposing() {
516
764
  if (this._composingBaseIndex === void 0) return;
765
+ this._currentSession = void 0;
517
766
  const toRevert = this._undoOperations.splice(this._composingBaseIndex);
518
767
  this._redoOperations.splice(this._composingRedoLength);
519
768
  this._composingBaseIndex = void 0;
@@ -565,204 +814,6 @@ var Tracker = class {
565
814
  }
566
815
  };
567
816
 
568
- // src/OperationProperties.ts
569
- var OperationProperties = class {
570
- constructor(trackedObject, property, type, validator, coalesceWithin) {
571
- this.trackedObject = trackedObject;
572
- this.property = property;
573
- this.type = type;
574
- this.validator = validator;
575
- this.coalesceWithin = coalesceWithin;
576
- }
577
- };
578
-
579
- // src/ExternallyAssigned.ts
580
- var AUTO_ID = /* @__PURE__ */ Symbol("autoId");
581
- function AutoId(_target, context) {
582
- context.addInitializer(function() {
583
- Object.defineProperty(Object.getPrototypeOf(this), AUTO_ID, {
584
- value: String(context.name),
585
- configurable: true
586
- });
587
- });
588
- }
589
- function getAutoIdProperty(proto) {
590
- return AUTO_ID in proto ? proto[AUTO_ID] : void 0;
591
- }
592
-
593
- // src/TrackedObjectStateMachine.ts
594
- function applyStateTransition(obj, event, direction, context) {
595
- if (event === "added") {
596
- applyAdded(obj, direction);
597
- } else if (event === "removed") {
598
- applyRemoved(obj, direction, context);
599
- } else {
600
- applyCommitted(obj, direction, context);
601
- }
602
- }
603
- function applyAdded(obj, direction) {
604
- if (direction === "undo") {
605
- obj._setState("Unchanged" /* Unchanged */);
606
- } else {
607
- obj._setState("Insert" /* Insert */);
608
- }
609
- }
610
- function applyRemoved(obj, direction, context) {
611
- if (direction === "undo") {
612
- const prev = context?.prevState ?? "Unchanged" /* Unchanged */;
613
- obj._setState(prev);
614
- if (prev === "Insert" /* Insert */) {
615
- if (context?.prevDirtyCounter !== void 0) {
616
- obj._setDirtyCounter(context.prevDirtyCounter);
617
- }
618
- }
619
- } else {
620
- if (obj.state === "Insert" /* Insert */) {
621
- obj._setState("Unchanged" /* Unchanged */);
622
- obj._setDirtyCounter(0);
623
- } else {
624
- obj._setState("Deleted" /* Deleted */);
625
- }
626
- }
627
- }
628
- function applyCommitted(obj, direction, context) {
629
- if (direction === "undo") {
630
- const prev = context?.prevState ?? "Unchanged" /* Unchanged */;
631
- if (prev === "Insert" /* Insert */) {
632
- obj._setState("Deleted" /* Deleted */);
633
- } else if (prev === "Deleted" /* Deleted */) {
634
- obj._setState("Insert" /* Insert */);
635
- } else if (prev === "Changed" /* Changed */) {
636
- obj._setState("Changed" /* Changed */);
637
- }
638
- } else {
639
- if ((context?.prevState === "Insert" /* Insert */ || context?.prevState === "Changed" /* Changed */) && context.autoIdProp && context.realId !== void 0) {
640
- obj[context.autoIdProp] = context.realId;
641
- }
642
- obj._setState("Unchanged" /* Unchanged */);
643
- obj._setDirtyCounter(0);
644
- }
645
- }
646
- function buildCommittedContext(obj, autoIdProp, keys) {
647
- const prevState = obj.state;
648
- let realId;
649
- if ((prevState === "Insert" /* Insert */ || prevState === "Changed" /* Changed */) && keys) {
650
- realId = keys.find((k) => k.trackingId === obj.trackingId)?.value;
651
- }
652
- return { prevState, autoIdProp, realId };
653
- }
654
-
655
- // src/TrackedObject.ts
656
- var TrackedObject = class {
657
- constructor(tracker) {
658
- this.tracker = tracker;
659
- this._dirtyCounter = 0;
660
- this._isValid = true;
661
- this._state = "Unchanged" /* Unchanged */;
662
- this.changed = new TypedEvent();
663
- this.trackedChanged = new TypedEvent();
664
- if (false) {
665
- throw new Error(`${this.constructor.name} must be created inside tracker.construct()`);
666
- }
667
- this.trackingId = tracker._nextTrackingId();
668
- this.validationMessages = /* @__PURE__ */ new Map();
669
- tracker._trackObject(this);
670
- }
671
- // ---- StateTarget interface (internal) ----
672
- /** @internal */
673
- get state() {
674
- return this._state;
675
- }
676
- /** @internal */
677
- _setState(value) {
678
- this._state = value;
679
- }
680
- /** @internal */
681
- _getDirtyCounter() {
682
- return this._dirtyCounter;
683
- }
684
- /** @internal */
685
- _setDirtyCounter(value) {
686
- this._dirtyCounter = value;
687
- }
688
- // ---- Public API ----
689
- get validationMessages() {
690
- return this._validationMessages ?? /* @__PURE__ */ new Map();
691
- }
692
- set validationMessages(value) {
693
- this._validationMessages = value;
694
- }
695
- get isValid() {
696
- return this._isValid;
697
- }
698
- set isValid(value) {
699
- const wasValid = this._isValid;
700
- this._isValid = value;
701
- if (wasValid !== value) {
702
- this.tracker._onValidityChanged(wasValid, value);
703
- }
704
- }
705
- get isDirty() {
706
- return this._dirtyCounter !== 0;
707
- }
708
- get dirtyCounter() {
709
- return this._dirtyCounter;
710
- }
711
- set dirtyCounter(value) {
712
- this._dirtyCounter = value;
713
- }
714
- /** @internal */
715
- _onCommitted(lastOp, keys) {
716
- const autoIdProp = getAutoIdProperty(Object.getPrototypeOf(this));
717
- const context = buildCommittedContext(this, autoIdProp, keys);
718
- const redoFn = () => applyStateTransition(this, "committed", "do", context);
719
- const undoFn = () => applyStateTransition(this, "committed", "undo", context);
720
- if (lastOp) {
721
- lastOp.updateOrAdd(redoFn, undoFn, new OperationProperties(this, "__state__", 4 /* Object */));
722
- }
723
- redoFn();
724
- }
725
- /** @internal */
726
- _markRemoved() {
727
- const prevState = this._state;
728
- const prevDirtyCounter = this._dirtyCounter;
729
- this.tracker._doAndTrack(
730
- () => applyStateTransition(this, "removed", "do"),
731
- () => applyStateTransition(this, "removed", "undo", { prevState, prevDirtyCounter }),
732
- new OperationProperties(this, "__state__", 4 /* Object */)
733
- );
734
- }
735
- /** @internal */
736
- _markAdded() {
737
- if (this._state !== "Unchanged" /* Unchanged */) return;
738
- if (this.tracker._isTrackingSuppressed) return;
739
- this.tracker._doAndTrack(
740
- () => applyStateTransition(this, "added", "do"),
741
- () => applyStateTransition(this, "added", "undo"),
742
- new OperationProperties(this, "__state__", 4 /* Object */)
743
- );
744
- }
745
- /** @internal */
746
- _validate(property, errorMessage) {
747
- if (errorMessage) {
748
- this.validationMessages.set(property, errorMessage);
749
- } else {
750
- this.validationMessages.delete(property);
751
- }
752
- this.validationMessages = new Map(this.validationMessages);
753
- this.isValid = this.validationMessages.size === 0;
754
- }
755
- /** @internal */
756
- _applyValidation(messages) {
757
- this.validationMessages = messages;
758
- this.isValid = messages.size === 0;
759
- }
760
- destroy() {
761
- DependencyTracker.clearDeps(this);
762
- this.tracker._untrackObject(this);
763
- }
764
- };
765
-
766
817
  // src/Tracked.ts
767
818
  function Tracked(validator, onChange, options) {
768
819
  function decorator(target, context) {
@@ -1259,6 +1310,7 @@ var TrackedCollectionChanged = class {
1259
1310
  TrackedCollectionChanged,
1260
1311
  TrackedObject,
1261
1312
  Tracker,
1313
+ TrackerSession,
1262
1314
  TypedEvent
1263
1315
  });
1264
1316
  //# sourceMappingURL=index.cjs.map