reactronic 0.21.600 → 0.21.604

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.
@@ -24,6 +24,7 @@ export interface ProfilingOptions {
24
24
  }
25
25
  export declare const TraceLevel: {
26
26
  ErrorsOnly: TraceOptions;
27
+ Reactions: TraceOptions;
27
28
  Transactions: TraceOptions;
28
29
  Operations: TraceOptions;
29
30
  Debug: TraceOptions;
@@ -20,6 +20,24 @@ exports.TraceLevel = {
20
20
  margin1: 0,
21
21
  margin2: 0,
22
22
  },
23
+ Reactions: {
24
+ silent: false,
25
+ transaction: false,
26
+ operation: false,
27
+ step: false,
28
+ monitor: false,
29
+ read: false,
30
+ write: false,
31
+ change: false,
32
+ obsolete: true,
33
+ error: true,
34
+ warning: true,
35
+ gc: false,
36
+ color: 37,
37
+ prefix: '',
38
+ margin1: 0,
39
+ margin2: 0,
40
+ },
23
41
  Transactions: {
24
42
  silent: false,
25
43
  transaction: true,
@@ -18,12 +18,13 @@ export interface Observer {
18
18
  readonly observables: Map<Observable, MemberInfo> | undefined;
19
19
  readonly obsoleteSince: number;
20
20
  hint(nop?: boolean): string;
21
- markObsoleteDueTo(observable: Observable, cause: MemberInfo, since: number, reactions: Observer[]): void;
21
+ markObsoleteDueTo(observable: Observable, memberName: MemberName, snapshot: AbstractSnapshot, holder: ObjectHolder, outer: string, since: number, reactions: Observer[]): void;
22
22
  runIfNotUpToDate(now: boolean, nothrow: boolean): void;
23
23
  }
24
24
  export declare type MemberName = PropertyKey;
25
25
  export interface MemberInfo {
26
- readonly revision: ObjectRevision;
26
+ readonly holder: ObjectHolder;
27
+ readonly snapshot: AbstractSnapshot;
27
28
  readonly memberName: MemberName;
28
29
  readonly usageCount: number;
29
30
  }
@@ -33,7 +34,7 @@ export declare class ObjectRevision {
33
34
  revision: ObjectRevision;
34
35
  };
35
36
  readonly data: any;
36
- readonly changes: Map<MemberName, Observer>;
37
+ readonly changes: Set<MemberName>;
37
38
  readonly conflicts: Map<MemberName, ObjectRevision>;
38
39
  constructor(snapshot: AbstractSnapshot, prev: ObjectRevision | undefined, data: object);
39
40
  }
@@ -16,7 +16,7 @@ class ObjectRevision {
16
16
  this.snapshot = snapshot;
17
17
  this.prev = { revision: prev || this };
18
18
  this.data = data;
19
- this.changes = new Map();
19
+ this.changes = new Set();
20
20
  this.conflicts = new Map();
21
21
  if (Dbg_1.Dbg.isOn)
22
22
  Object.freeze(this);
@@ -1,7 +1,7 @@
1
1
  import { F } from '../util/Utils';
2
2
  import { MemberOptions } from '../Options';
3
3
  import { Controller } from '../Controller';
4
- import { ObjectRevision, MemberName, ObjectHolder, Observable, Observer, MemberInfo } from './Data';
4
+ import { MemberName, ObjectHolder, Observable, Observer, MemberInfo, AbstractSnapshot } from './Data';
5
5
  import { Transaction } from './Transaction';
6
6
  import { OptionsImpl } from './Hooks';
7
7
  export declare class OperationController extends Controller<any> {
@@ -39,18 +39,18 @@ declare class Operation extends Observable implements Observer {
39
39
  readonly margin: number;
40
40
  readonly transaction: Transaction;
41
41
  readonly controller: OperationController;
42
- readonly revision: ObjectRevision;
42
+ readonly snapshot: AbstractSnapshot;
43
43
  observables: Map<Observable, MemberInfo> | undefined;
44
44
  options: OptionsImpl;
45
- cause: MemberInfo | undefined;
45
+ cause: string | undefined;
46
46
  args: any[];
47
47
  result: any;
48
48
  error: any;
49
49
  started: number;
50
- obsoleteDueTo: MemberInfo | undefined;
50
+ obsoleteDueTo: string | undefined;
51
51
  obsoleteSince: number;
52
52
  successor: Operation | undefined;
53
- constructor(controller: OperationController, revision: ObjectRevision, prev: Operation | OptionsImpl);
53
+ constructor(controller: OperationController, snapshot: AbstractSnapshot, prev: Operation | OptionsImpl);
54
54
  get isOperation(): boolean;
55
55
  get selfSnapshotId(): number;
56
56
  hint(): string;
@@ -61,7 +61,7 @@ declare class Operation extends Observable implements Observer {
61
61
  dependencies(): string[];
62
62
  wrap<T>(func: F<T>): F<T>;
63
63
  run(proxy: any, args: any[] | undefined): void;
64
- markObsoleteDueTo(observable: Observable, cause: MemberInfo, since: number, reactions: Observer[]): void;
64
+ markObsoleteDueTo(observable: Observable, memberName: MemberName, snapshot: AbstractSnapshot, holder: ObjectHolder, outer: string, since: number, reactions: Observer[]): void;
65
65
  runIfNotUpToDate(now: boolean, nothrow: boolean): void;
66
66
  isNotUpToDate(): boolean;
67
67
  reenterOver(head: Operation): this;
@@ -79,7 +79,7 @@ declare class Operation extends Observable implements Observer {
79
79
  private static propagateAllChangesThroughSubscriptions;
80
80
  private static revokeAllSubscriptions;
81
81
  private static propagateMemberChangeThroughSubscriptions;
82
- private static enqueueDetectedReactions;
82
+ private static enqueueReactionsToRun;
83
83
  private static runQueuedReactionsLoop;
84
84
  private unsubscribeFromAllObservables;
85
85
  private subscribeTo;
@@ -12,7 +12,7 @@ const Hooks_1 = require("./Hooks");
12
12
  const TransactionJournal_1 = require("./TransactionJournal");
13
13
  const ROOT_ARGS = [];
14
14
  const ROOT_HOLDER = new Data_1.ObjectHolder(undefined, undefined, Hooks_1.Hooks.proxy, Snapshot_1.ROOT_REV, 'root-holder');
15
- const ROOT_TRIGGER = { revision: Snapshot_1.ROOT_REV, memberName: 'root-trigger', usageCount: 0 };
15
+ const INITIAL_CAUSE = 'initial';
16
16
  class OperationController extends Controller_1.Controller {
17
17
  constructor(ownHolder, memberName) {
18
18
  super();
@@ -36,7 +36,7 @@ class OperationController extends Controller_1.Controller {
36
36
  const op = oc.operation;
37
37
  const opts = op.options;
38
38
  if (!oc.isUpToDate && oc.revision.data[Data_1.Meta.Disposed] === undefined
39
- && (!weak || op.cause === ROOT_TRIGGER || !op.successor ||
39
+ && (!weak || op.cause === INITIAL_CAUSE || !op.successor ||
40
40
  op.successor.transaction.isFinished)) {
41
41
  const outerOpts = (_a = Operation.current) === null || _a === void 0 ? void 0 : _a.options;
42
42
  const standalone = weak || opts.standalone || opts.kind === Options_1.Kind.Reaction ||
@@ -45,13 +45,13 @@ class OperationController extends Controller_1.Controller {
45
45
  oc.revision.prev.revision !== Snapshot_1.ROOT_REV));
46
46
  const token = opts.noSideEffects ? this : undefined;
47
47
  const oc2 = this.run(oc, standalone, opts, token, args);
48
- const ctx2 = oc2.operation.revision.snapshot;
48
+ const ctx2 = oc2.operation.snapshot;
49
49
  if (!weak || ctx === ctx2 || (ctx2.sealed && ctx.timestamp >= ctx2.timestamp))
50
50
  oc = oc2;
51
51
  }
52
52
  else if (Dbg_1.Dbg.isOn && Dbg_1.Dbg.trace.operation && (opts.trace === undefined ||
53
53
  opts.trace.operation === undefined || opts.trace.operation === true))
54
- Dbg_1.Dbg.log(Transaction_1.Transaction.current.isFinished ? '' : '║', ' (=)', `${Snapshot_1.Dump.rev(oc.revision, this.memberName)} result is reused from T${oc.operation.transaction.id}[${oc.operation.transaction.hint}]`);
54
+ Dbg_1.Dbg.log(Transaction_1.Transaction.current.isFinished ? '' : '║', ' (=)', `${Snapshot_1.Dump.rev2(oc.operation.controller.ownHolder, oc.snapshot, this.memberName)} result is reused from T${oc.operation.transaction.id}[${oc.operation.transaction.hint}]`);
55
55
  const t = oc.operation;
56
56
  Snapshot_1.Snapshot.markUsed(t, oc.revision, this.memberName, this.ownHolder, t.options.kind, weak);
57
57
  return t;
@@ -108,8 +108,8 @@ class OperationController extends Controller_1.Controller {
108
108
  const ctx = Snapshot_1.Snapshot.current();
109
109
  const r = ctx.seekRevision(this.ownHolder, this.memberName);
110
110
  const op = this.peekFromRevision(r, args);
111
- const isValid = op.options.kind !== Options_1.Kind.Transaction && op.cause !== ROOT_TRIGGER &&
112
- (ctx === op.revision.snapshot || ctx.timestamp < op.obsoleteSince) &&
111
+ const isValid = op.options.kind !== Options_1.Kind.Transaction && op.cause !== INITIAL_CAUSE &&
112
+ (ctx === op.snapshot || ctx.timestamp < op.obsoleteSince) &&
113
113
  (!op.options.sensitiveArgs || args === undefined ||
114
114
  op.args.length === args.length && op.args.every((t, i) => t === args[i])) ||
115
115
  r.data[Data_1.Meta.Disposed] !== undefined;
@@ -126,8 +126,8 @@ class OperationController extends Controller_1.Controller {
126
126
  const ctx = Snapshot_1.Snapshot.edit();
127
127
  const r = ctx.getEditableRevision(h, m, Data_1.Meta.Holder, this);
128
128
  let op = this.peekFromRevision(r, undefined);
129
- if (op.revision !== r) {
130
- const op2 = new Operation(this, r, op);
129
+ if (op.snapshot !== r.snapshot) {
130
+ const op2 = new Operation(this, r.snapshot, op);
131
131
  r.data[m] = op2.reenterOver(op);
132
132
  ctx.bumpBy(r.prev.revision.snapshot.timestamp);
133
133
  Snapshot_1.Snapshot.markEdited(op, op2, true, r, m, h);
@@ -147,10 +147,10 @@ class OperationController extends Controller_1.Controller {
147
147
  let op2 = r2.data[m];
148
148
  if (op2.controller !== this) {
149
149
  r2 = Snapshot_1.Snapshot.edit().getEditableRevision(h, m, Data_1.Meta.Holder, this);
150
- const t = new Operation(this, r2, op2);
150
+ const t = new Operation(this, r2.snapshot, op2);
151
151
  if (args)
152
152
  t.args = args;
153
- t.cause = ROOT_TRIGGER;
153
+ t.cause = INITIAL_CAUSE;
154
154
  r2.data[m] = t;
155
155
  Snapshot_1.Snapshot.markEdited(op2, t, true, r2, m, h);
156
156
  op2 = t;
@@ -188,17 +188,17 @@ class OperationController extends Controller_1.Controller {
188
188
  static markObsolete(self) {
189
189
  const oc = self.peek(undefined);
190
190
  const ctx = oc.snapshot;
191
- oc.operation.markObsoleteDueTo(oc.operation, { revision: Snapshot_1.ROOT_REV, memberName: self.memberName, usageCount: 0 }, ctx.timestamp, ctx.reactions);
191
+ oc.operation.markObsoleteDueTo(oc.operation, self.memberName, Snapshot_1.ROOT_REV.snapshot, ROOT_HOLDER, INITIAL_CAUSE, ctx.timestamp, ctx.reactions);
192
192
  }
193
193
  }
194
194
  exports.OperationController = OperationController;
195
195
  class Operation extends Data_1.Observable {
196
- constructor(controller, revision, prev) {
196
+ constructor(controller, snapshot, prev) {
197
197
  super(undefined);
198
198
  this.margin = Operation.current ? Operation.current.margin + 1 : 1;
199
199
  this.transaction = Transaction_1.Transaction.current;
200
200
  this.controller = controller;
201
- this.revision = revision;
201
+ this.snapshot = snapshot;
202
202
  this.observables = new Map();
203
203
  if (prev instanceof Operation) {
204
204
  this.options = prev.options;
@@ -216,28 +216,24 @@ class Operation extends Data_1.Observable {
216
216
  this.successor = undefined;
217
217
  }
218
218
  get isOperation() { return true; }
219
- get selfSnapshotId() { return this.revision.snapshot.id; }
220
- hint() { return `${Snapshot_1.Dump.rev(this.revision, this.controller.memberName)}`; }
219
+ get selfSnapshotId() { return this.snapshot.id; }
220
+ hint() { return `${Snapshot_1.Dump.rev2(this.controller.ownHolder, this.snapshot, this.controller.memberName)}`; }
221
221
  get order() { return this.options.order; }
222
222
  get ['#this']() {
223
223
  return `Operation: ${this.why()}`;
224
224
  }
225
225
  why() {
226
- let ms = Date.now();
227
- const prev = this.revision.prev.revision.data[this.controller.memberName];
228
- if (prev instanceof Operation)
229
- ms = prev.started !== 0 ? Math.abs(this.started || ms) - Math.abs(prev.started) : Infinity;
230
- let trigger;
226
+ let cause;
231
227
  if (this.cause)
232
- trigger = ` << ${propagationHint(this.cause, true).join(' << ')}`;
228
+ cause = ` << ${this.cause}`;
233
229
  else if (this.controller.options.kind === Options_1.Kind.Transaction)
234
- trigger = ' << operation';
230
+ cause = ' << operation';
235
231
  else
236
- trigger = ` << T${this.revision.snapshot.id}[${this.revision.snapshot.hint}]`;
237
- return `${this.hint()}${trigger} (${ms !== Infinity ? `${ms}ms since previous run` : 'initial run'})`;
232
+ cause = ` << T${this.snapshot.id}[${this.snapshot.hint}]`;
233
+ return `${this.hint()}${cause}`;
238
234
  }
239
235
  briefWhy() {
240
- return this.cause ? propagationHint(this.cause, false)[0] : ROOT_HOLDER.hint;
236
+ return this.why();
241
237
  }
242
238
  dependencies() {
243
239
  throw (0, Dbg_1.misuse)('not implemented yet');
@@ -266,31 +262,33 @@ class Operation extends Data_1.Observable {
266
262
  else
267
263
  this.result = Promise.reject(this.error);
268
264
  }
269
- markObsoleteDueTo(observable, cause, since, reactions) {
265
+ markObsoleteDueTo(observable, memberName, snapshot, holder, outer, since, reactions) {
270
266
  var _a, _b, _c;
271
267
  if (this.observables !== undefined) {
272
- if (observable.isOperation || this !== cause.revision.changes.get(cause.memberName)) {
268
+ const skip = !observable.isOperation &&
269
+ snapshot === this.snapshot;
270
+ if (!skip) {
271
+ const why = `${Snapshot_1.Dump.rev2(holder, snapshot, memberName)} << ${outer}`;
273
272
  this.unsubscribeFromAllObservables();
274
- this.obsoleteDueTo = cause;
273
+ this.obsoleteDueTo = why;
275
274
  this.obsoleteSince = since;
276
275
  const isReaction = this.options.kind === Options_1.Kind.Reaction;
277
276
  if (Dbg_1.Dbg.isOn && (Dbg_1.Dbg.trace.obsolete || ((_a = this.options.trace) === null || _a === void 0 ? void 0 : _a.obsolete)))
278
- Dbg_1.Dbg.log(Dbg_1.Dbg.trace.transaction && !Snapshot_1.Snapshot.current().sealed ? '║' : ' ', isReaction ? '█' : '▒', isReaction && cause.revision === Snapshot_1.ROOT_REV
277
+ Dbg_1.Dbg.log(Dbg_1.Dbg.trace.transaction && !Snapshot_1.Snapshot.current().sealed ? '║' : ' ', isReaction ? '█' : '▒', isReaction && snapshot === Snapshot_1.ROOT_REV.snapshot
279
278
  ? `${this.hint()} is a reaction and will run automatically (order ${this.options.order})`
280
- : `${this.hint()} is obsolete due to ${Snapshot_1.Dump.rev(cause.revision, cause.memberName)} since v${since}${isReaction ? ` and will run automatically (order ${this.options.order})` : ''}`);
279
+ : `${this.hint()} is obsolete due to ${Snapshot_1.Dump.rev2(holder, snapshot, memberName)} since v${since}${isReaction ? ` and will run automatically (order ${this.options.order})` : ''}`);
281
280
  if (isReaction)
282
281
  reactions.push(this);
283
282
  else
284
- (_b = this.observers) === null || _b === void 0 ? void 0 : _b.forEach(c => c.markObsoleteDueTo(this, { revision: this.revision, memberName: this.controller.memberName, usageCount: 0 }, since, reactions));
283
+ (_b = this.observers) === null || _b === void 0 ? void 0 : _b.forEach(c => c.markObsoleteDueTo(this, this.controller.memberName, this.snapshot, this.controller.ownHolder, why, since, reactions));
285
284
  const tran = this.transaction;
286
- if (tran.snapshot === cause.revision.snapshot) {
287
- (0, Dbg_1.misuse)('not implemented: running reactions within original transaction');
285
+ if (tran.snapshot === snapshot) {
288
286
  }
289
287
  else if (!tran.isFinished && this !== observable)
290
- tran.cancel(new Error(`T${tran.id}[${tran.hint}] is canceled due to obsolete ${Snapshot_1.Dump.rev(cause.revision, cause.memberName)} changed by T${cause.revision.snapshot.id}[${cause.revision.snapshot.hint}]`), null);
288
+ tran.cancel(new Error(`T${tran.id}[${tran.hint}] is canceled due to obsolete ${Snapshot_1.Dump.rev2(holder, snapshot, memberName)} changed by T${snapshot.id}[${snapshot.hint}]`), null);
291
289
  }
292
290
  else if (Dbg_1.Dbg.isOn && (Dbg_1.Dbg.trace.obsolete || ((_c = this.options.trace) === null || _c === void 0 ? void 0 : _c.obsolete)))
293
- Dbg_1.Dbg.log(' ', 'x', `${this.hint()} is not obsolete due to its own change to ${Snapshot_1.Dump.rev(cause.revision, cause.memberName)}`);
291
+ Dbg_1.Dbg.log(' ', 'x', `${this.hint()} is not obsolete due to its own change to ${Snapshot_1.Dump.rev2(holder, snapshot, memberName)}`);
294
292
  }
295
293
  }
296
294
  runIfNotUpToDate(now, nothrow) {
@@ -407,6 +405,7 @@ class Operation extends Data_1.Observable {
407
405
  Dbg_1.Dbg.log('║', `${op}`, `${this.hint()} ${message}`, ms, highlight);
408
406
  if (ms > (main ? Hooks_1.Hooks.mainThreadBlockingWarningThreshold : Hooks_1.Hooks.asyncActionDurationWarningThreshold))
409
407
  Dbg_1.Dbg.log('', '[!]', this.why(), ms, main ? ' *** main thread is too busy ***' : ' *** async is too long ***');
408
+ this.cause = undefined;
410
409
  if (this.options.monitor)
411
410
  this.monitorLeave(this.options.monitor);
412
411
  }
@@ -451,19 +450,19 @@ class Operation extends Data_1.Observable {
451
450
  ctx.bumpBy(r.snapshot.timestamp);
452
451
  const t = weak ? -1 : ctx.timestamp;
453
452
  if (!op.subscribeTo(observable, r, m, h, t))
454
- op.markObsoleteDueTo(observable, { revision: r, memberName: m, usageCount: 0 }, ctx.timestamp, ctx.reactions);
453
+ op.markObsoleteDueTo(observable, m, r.snapshot, h, INITIAL_CAUSE, ctx.timestamp, ctx.reactions);
455
454
  }
456
455
  }
457
456
  }
458
457
  static markEdited(oldValue, newValue, edited, r, m, h) {
459
- edited ? r.changes.set(m, Operation.current) : r.changes.delete(m);
458
+ edited ? r.changes.add(m) : r.changes.delete(m);
460
459
  if (Dbg_1.Dbg.isOn && Dbg_1.Dbg.trace.write)
461
- edited ? Dbg_1.Dbg.log('║', ' ✎', `${Snapshot_1.Dump.rev(r, m)} is changed from ${valueHint(oldValue, m)} to ${valueHint(newValue, m)}`) : Dbg_1.Dbg.log('║', ' ✎', `${Snapshot_1.Dump.rev(r, m)} is changed from ${valueHint(oldValue, m)} to ${valueHint(newValue, m)}`, undefined, ' (same as previous)');
460
+ edited ? Dbg_1.Dbg.log('║', ' ✎', `${Snapshot_1.Dump.rev2(h, r.snapshot, m)} is changed from ${valueHint(oldValue, m)} to ${valueHint(newValue, m)}`) : Dbg_1.Dbg.log('║', ' ✎', `${Snapshot_1.Dump.rev2(h, r.snapshot, m)} is changed from ${valueHint(oldValue, m)} to ${valueHint(newValue, m)}`, undefined, ' (same as previous)');
462
461
  }
463
462
  static isConflicting(oldValue, newValue) {
464
463
  let result = oldValue !== newValue;
465
464
  if (result)
466
- result = oldValue instanceof Operation && oldValue.cause !== ROOT_TRIGGER;
465
+ result = oldValue instanceof Operation && oldValue.cause !== INITIAL_CAUSE;
467
466
  return result;
468
467
  }
469
468
  static propagateAllChangesThroughSubscriptions(snapshot) {
@@ -483,30 +482,39 @@ class Operation extends Data_1.Observable {
483
482
  static revokeAllSubscriptions(snapshot) {
484
483
  snapshot.changeset.forEach((r, h) => r.changes.forEach((o, m) => Operation.propagateMemberChangeThroughSubscriptions(true, snapshot.timestamp, r, m, h, undefined)));
485
484
  }
486
- static propagateMemberChangeThroughSubscriptions(discard, timestamp, r, m, h, reactions) {
485
+ static propagateMemberChangeThroughSubscriptions(unsubscribe, timestamp, r, m, h, reactions) {
487
486
  var _a;
487
+ const curr = r.data[m];
488
488
  if (reactions) {
489
489
  const prev = r.prev.revision.data[m];
490
490
  if (prev !== undefined && prev instanceof Data_1.Observable) {
491
- const cause = { revision: r, memberName: m, usageCount: 0 };
492
- if (prev instanceof Operation && (prev.obsoleteSince === Snapshot_1.MAX_TIMESTAMP || prev.obsoleteSince <= 0)) {
493
- prev.obsoleteDueTo = cause;
494
- prev.obsoleteSince = timestamp;
495
- prev.unsubscribeFromAllObservables();
491
+ const why = `T${r.snapshot.id}[${r.snapshot.hint}]`;
492
+ if (prev instanceof Operation) {
493
+ if ((prev.obsoleteSince === Snapshot_1.MAX_TIMESTAMP || prev.obsoleteSince <= 0)) {
494
+ prev.obsoleteDueTo = why;
495
+ prev.obsoleteSince = timestamp;
496
+ prev.unsubscribeFromAllObservables();
497
+ }
498
+ const prevSuccessor = prev.successor;
499
+ if (prevSuccessor !== curr) {
500
+ if (prevSuccessor && !prevSuccessor.transaction.isFinished)
501
+ prevSuccessor.transaction.cancel(new Error(`T${prevSuccessor.transaction.id}[${prevSuccessor.transaction.hint}] is canceled by T${r.snapshot.id}[${r.snapshot.hint}] and will not run anymore`), null);
502
+ }
503
+ else
504
+ prev.successor = undefined;
496
505
  }
497
- (_a = prev.observers) === null || _a === void 0 ? void 0 : _a.forEach(c => c.markObsoleteDueTo(prev, cause, timestamp, reactions));
506
+ (_a = prev.observers) === null || _a === void 0 ? void 0 : _a.forEach(c => c.markObsoleteDueTo(prev, m, r.snapshot, h, why, timestamp, reactions));
498
507
  }
499
508
  }
500
- const curr = r.data[m];
501
509
  if (curr instanceof Operation) {
502
- if (curr.revision === r && curr.observables !== undefined) {
510
+ if (curr.snapshot === r.snapshot && curr.observables !== undefined) {
503
511
  if (Hooks_1.Hooks.repetitiveUsageWarningThreshold < Number.MAX_SAFE_INTEGER) {
504
512
  curr.observables.forEach((hint, v) => {
505
513
  if (hint.usageCount > Hooks_1.Hooks.repetitiveUsageWarningThreshold)
506
- Dbg_1.Dbg.log('', '[!]', `${curr.hint()} uses ${Snapshot_1.Dump.rev(hint.revision, hint.memberName)} ${hint.usageCount} times (consider remembering it in a local variable)`, 0, ' *** WARNING ***');
514
+ Dbg_1.Dbg.log('', '[!]', `${curr.hint()} uses ${Snapshot_1.Dump.rev2(hint.holder, hint.snapshot, hint.memberName)} ${hint.usageCount} times (consider remembering it in a local variable)`, 0, ' *** WARNING ***');
507
515
  });
508
516
  }
509
- if (discard)
517
+ if (unsubscribe)
510
518
  curr.unsubscribeFromAllObservables();
511
519
  }
512
520
  }
@@ -519,10 +527,10 @@ class Operation extends Data_1.Observable {
519
527
  curr.observers = undefined;
520
528
  }
521
529
  }
522
- static enqueueDetectedReactions(snapshot) {
530
+ static enqueueReactionsToRun(reactions) {
523
531
  const queue = Operation.queuedReactions;
524
532
  const isReactionLoopRequired = queue.length === 0;
525
- for (const r of snapshot.reactions)
533
+ for (const r of reactions)
526
534
  queue.push(r);
527
535
  if (isReactionLoopRequired)
528
536
  OperationController.runWithin(undefined, Operation.runQueuedReactionsLoop);
@@ -543,7 +551,7 @@ class Operation extends Data_1.Observable {
543
551
  var _a;
544
552
  value.observers.delete(this);
545
553
  if (Dbg_1.Dbg.isOn && (Dbg_1.Dbg.trace.read || ((_a = this.options.trace) === null || _a === void 0 ? void 0 : _a.read)))
546
- Dbg_1.Dbg.log(Dbg_1.Dbg.trace.transaction && !Snapshot_1.Snapshot.current().sealed ? '║' : ' ', '-', `${this.hint()} is unsubscribed from ${Snapshot_1.Dump.rev(hint.revision, hint.memberName)}`);
554
+ Dbg_1.Dbg.log(Dbg_1.Dbg.trace.transaction && !Snapshot_1.Snapshot.current().sealed ? '║' : ' ', '-', `${this.hint()} is unsubscribed from ${Snapshot_1.Dump.rev2(hint.holder, hint.snapshot, hint.memberName)}`);
547
555
  });
548
556
  this.observables = undefined;
549
557
  }
@@ -559,18 +567,18 @@ class Operation extends Data_1.Observable {
559
567
  if (this.observables !== undefined) {
560
568
  if (!observable.observers)
561
569
  observable.observers = new Set();
562
- const info = { revision: r, memberName: m, usageCount: times };
570
+ const info = { holder: h, snapshot: r.snapshot, memberName: m, usageCount: times };
563
571
  observable.observers.add(this);
564
572
  this.observables.set(observable, info);
565
573
  if (Dbg_1.Dbg.isOn && (Dbg_1.Dbg.trace.read || ((_a = this.options.trace) === null || _a === void 0 ? void 0 : _a.read)))
566
- Dbg_1.Dbg.log('║', ' ∞ ', `${this.hint()} is subscribed to ${Snapshot_1.Dump.rev(r, m)}${info.usageCount > 1 ? ` (${info.usageCount} times)` : ''}`);
574
+ Dbg_1.Dbg.log('║', ' ∞ ', `${this.hint()} is subscribed to ${Snapshot_1.Dump.rev2(h, r.snapshot, m)}${info.usageCount > 1 ? ` (${info.usageCount} times)` : ''}`);
567
575
  }
568
576
  else if (Dbg_1.Dbg.isOn && (Dbg_1.Dbg.trace.read || ((_b = this.options.trace) === null || _b === void 0 ? void 0 : _b.read)))
569
- Dbg_1.Dbg.log('║', ' x ', `${this.hint()} is obsolete and is NOT subscribed to ${Snapshot_1.Dump.rev(r, m)}`);
577
+ Dbg_1.Dbg.log('║', ' x ', `${this.hint()} is obsolete and is NOT subscribed to ${Snapshot_1.Dump.rev2(h, r.snapshot, m)}`);
570
578
  }
571
579
  else {
572
580
  if (Dbg_1.Dbg.isOn && (Dbg_1.Dbg.trace.read || ((_c = this.options.trace) === null || _c === void 0 ? void 0 : _c.read)))
573
- Dbg_1.Dbg.log('║', ' x ', `${this.hint()} is NOT subscribed to already obsolete ${Snapshot_1.Dump.rev(r, m)}`);
581
+ Dbg_1.Dbg.log('║', ' x ', `${this.hint()} is NOT subscribed to already obsolete ${Snapshot_1.Dump.rev2(h, r.snapshot, m)}`);
574
582
  }
575
583
  return ok;
576
584
  }
@@ -593,7 +601,7 @@ class Operation extends Data_1.Observable {
593
601
  let op = initial[m];
594
602
  const ctl = op ? op.controller : new OperationController(ROOT_HOLDER, m);
595
603
  const opts = op ? op.options : Hooks_1.OptionsImpl.INITIAL;
596
- initial[m] = op = new Operation(ctl, Snapshot_1.ROOT_REV, new Hooks_1.OptionsImpl(getter, setter, opts, options, implicit));
604
+ initial[m] = op = new Operation(ctl, Snapshot_1.ROOT_REV.snapshot, new Hooks_1.OptionsImpl(getter, setter, opts, options, implicit));
597
605
  if (op.options.kind === Options_1.Kind.Reaction && op.options.throttling < Number.MAX_SAFE_INTEGER) {
598
606
  const reactions = Data_1.Meta.acquire(proto, Data_1.Meta.Reactions);
599
607
  reactions[m] = op;
@@ -606,14 +614,13 @@ class Operation extends Data_1.Observable {
606
614
  }
607
615
  static init() {
608
616
  Object.freeze(ROOT_ARGS);
609
- Object.freeze(ROOT_TRIGGER);
610
617
  Dbg_1.Dbg.getMergedTraceOptions = getMergedTraceOptions;
611
618
  Snapshot_1.Snapshot.markUsed = Operation.markUsed;
612
619
  Snapshot_1.Snapshot.markEdited = Operation.markEdited;
613
620
  Snapshot_1.Snapshot.isConflicting = Operation.isConflicting;
614
621
  Snapshot_1.Snapshot.propagateAllChangesThroughSubscriptions = Operation.propagateAllChangesThroughSubscriptions;
615
622
  Snapshot_1.Snapshot.revokeAllSubscriptions = Operation.revokeAllSubscriptions;
616
- Snapshot_1.Snapshot.enqueueDetectedReactions = Operation.enqueueDetectedReactions;
623
+ Snapshot_1.Snapshot.enqueueReactionsToRun = Operation.enqueueReactionsToRun;
617
624
  Hooks_1.Hooks.createControllerAndGetHook = Operation.createControllerAndGetHook;
618
625
  Hooks_1.Hooks.rememberOperationOptions = Operation.rememberOperationOptions;
619
626
  Promise.prototype.then = reactronicHookedThen;
@@ -642,18 +649,6 @@ class Operation extends Data_1.Observable {
642
649
  Operation.current = undefined;
643
650
  Operation.queuedReactions = [];
644
651
  Operation.deferredReactions = [];
645
- function propagationHint(cause, full) {
646
- const result = [];
647
- let observable = cause.revision.data[cause.memberName];
648
- while (observable instanceof Operation && observable.obsoleteDueTo) {
649
- full && result.push(Snapshot_1.Dump.rev(cause.revision, cause.memberName));
650
- cause = observable.obsoleteDueTo;
651
- observable = cause.revision.data[cause.memberName];
652
- }
653
- result.push(Snapshot_1.Dump.rev(cause.revision, cause.memberName));
654
- full && result.push(cause.revision.snapshot.hint);
655
- return result;
656
- }
657
652
  function valueHint(value, m) {
658
653
  let result = '';
659
654
  if (Array.isArray(value))
@@ -663,7 +658,7 @@ function valueHint(value, m) {
663
658
  else if (value instanceof Map)
664
659
  result = `Map(${value.size})`;
665
660
  else if (value instanceof Operation)
666
- result = `${Snapshot_1.Dump.rev(value.revision, m)}`;
661
+ result = `${Snapshot_1.Dump.rev2(value.controller.ownHolder, value.snapshot, m)}`;
667
662
  else if (value === Data_1.Meta.Disposed)
668
663
  result = '<disposed>';
669
664
  else if (value !== undefined && value !== null)
@@ -17,8 +17,8 @@ export declare class Snapshot implements AbstractSnapshot {
17
17
  get timestamp(): number;
18
18
  private stamp;
19
19
  private bumper;
20
- readonly changeset: Map<ObjectHolder, ObjectRevision>;
21
- readonly reactions: Observer[];
20
+ changeset: Map<ObjectHolder, ObjectRevision>;
21
+ reactions: Observer[];
22
22
  sealed: boolean;
23
23
  constructor(options: SnapshotOptions | null);
24
24
  static current: () => Snapshot;
@@ -28,7 +28,7 @@ export declare class Snapshot implements AbstractSnapshot {
28
28
  static isConflicting: (oldValue: any, newValue: any) => boolean;
29
29
  static propagateAllChangesThroughSubscriptions: (snapshot: Snapshot) => void;
30
30
  static revokeAllSubscriptions: (snapshot: Snapshot) => void;
31
- static enqueueDetectedReactions: (snapshot: Snapshot) => void;
31
+ static enqueueReactionsToRun: (reactions: Array<Observer>) => void;
32
32
  seekRevision(h: ObjectHolder, m: MemberName): ObjectRevision;
33
33
  getCurrentRevision(h: ObjectHolder, m: MemberName): ObjectRevision;
34
34
  getEditableRevision(h: ObjectHolder, m: MemberName, value: any, token?: any): ObjectRevision;
@@ -40,17 +40,17 @@ export declare class Snapshot implements AbstractSnapshot {
40
40
  bumpBy(timestamp: number): void;
41
41
  rebase(): ObjectRevision[] | undefined;
42
42
  private static merge;
43
- applyOrDiscard(error?: any): void;
43
+ applyOrDiscard(error?: any): Array<Observer>;
44
44
  static sealObjectRevision(h: ObjectHolder, r: ObjectRevision): void;
45
45
  static sealObservable(observable: Observable | symbol, m: MemberName, typeName: string): void;
46
- collectGarbage(): void;
47
46
  static freezeObjectRevision(r: ObjectRevision): ObjectRevision;
48
- private triggerGarbageCollection;
47
+ triggerGarbageCollection(): void;
49
48
  private unlinkHistory;
50
49
  static _init(): void;
51
50
  }
52
51
  export declare class Dump {
53
52
  static obj(h: ObjectHolder | undefined, m?: MemberName | undefined, stamp?: number, op?: number, xop?: number, typeless?: boolean): string;
53
+ static rev2(h: ObjectHolder, s: AbstractSnapshot, m?: MemberName, value?: Observable): string;
54
54
  static rev(r: ObjectRevision, m?: MemberName): string;
55
55
  static conflicts(conflicts: ObjectRevision[]): string;
56
56
  static conflictingMemberHint(m: MemberName, ours: ObjectRevision, theirs: ObjectRevision): string;
@@ -27,6 +27,8 @@ Object.defineProperty(Data_1.ObjectHolder.prototype, '#this', {
27
27
  return result;
28
28
  },
29
29
  });
30
+ const EMPTY_ARRAY = Object.freeze([]);
31
+ const EMPTY_MAP = Utils_1.Utils.freezeMap(new Map());
30
32
  class Snapshot {
31
33
  constructor(options) {
32
34
  this.id = ++Snapshot.idGen;
@@ -128,14 +130,14 @@ class Snapshot {
128
130
  if (this.changeset.size > 0) {
129
131
  this.changeset.forEach((r, h) => {
130
132
  if (r.prev.revision !== h.head) {
131
- const merged = Snapshot.merge(r, h.head);
133
+ const merged = Snapshot.merge(h, r);
132
134
  if (r.conflicts.size > 0) {
133
135
  if (!conflicts)
134
136
  conflicts = [];
135
137
  conflicts.push(r);
136
138
  }
137
139
  if (Dbg_1.Dbg.isOn && Dbg_1.Dbg.trace.transaction)
138
- Dbg_1.Dbg.log('╠╝', '', `${Dump.rev(r)} is merged with ${Dump.rev(h.head)} among ${merged} properties with ${r.conflicts.size} conflicts.`);
140
+ Dbg_1.Dbg.log('╠╝', '', `${Dump.rev2(h, r.snapshot)} is merged with ${Dump.rev2(h, h.head.snapshot)} among ${merged} properties with ${r.conflicts.size} conflicts.`);
139
141
  }
140
142
  });
141
143
  if (this.options.token === undefined) {
@@ -152,8 +154,9 @@ class Snapshot {
152
154
  }
153
155
  return conflicts;
154
156
  }
155
- static merge(ours, head) {
157
+ static merge(h, ours) {
156
158
  let counter = 0;
159
+ const head = h.head;
157
160
  const disposed = head.changes.has(Data_1.Meta.Disposed);
158
161
  const merged = Object.assign({}, head.data);
159
162
  ours.changes.forEach((o, m) => {
@@ -162,7 +165,7 @@ class Snapshot {
162
165
  if (disposed || m === Data_1.Meta.Disposed) {
163
166
  if (disposed !== (m === Data_1.Meta.Disposed)) {
164
167
  if (Dbg_1.Dbg.isOn && Dbg_1.Dbg.trace.change)
165
- Dbg_1.Dbg.log('║╠', '', `${Dump.rev(ours, m)} <> ${Dump.rev(head, m)}`, 0, ' *** CONFLICT ***');
168
+ Dbg_1.Dbg.log('║╠', '', `${Dump.rev2(h, ours.snapshot, m)} <> ${Dump.rev2(h, head.snapshot, m)}`, 0, ' *** CONFLICT ***');
166
169
  ours.conflicts.set(m, head);
167
170
  }
168
171
  }
@@ -171,7 +174,7 @@ class Snapshot {
171
174
  if (conflict)
172
175
  ours.conflicts.set(m, head);
173
176
  if (Dbg_1.Dbg.isOn && Dbg_1.Dbg.trace.change)
174
- Dbg_1.Dbg.log('║╠', '', `${Dump.rev(ours, m)} ${conflict ? '<>' : '=='} ${Dump.rev(head, m)}`, 0, conflict ? ' *** CONFLICT ***' : undefined);
177
+ Dbg_1.Dbg.log('║╠', '', `${Dump.rev2(h, ours.snapshot, m)} ${conflict ? '<>' : '=='} ${Dump.rev2(h, head.snapshot, m)}`, 0, conflict ? ' *** CONFLICT ***' : undefined);
175
178
  }
176
179
  });
177
180
  Utils_1.Utils.copyAllMembers(merged, ours.data);
@@ -200,7 +203,7 @@ class Snapshot {
200
203
  const members = [];
201
204
  r.changes.forEach((o, m) => members.push(m.toString()));
202
205
  const s = members.join(', ');
203
- Dbg_1.Dbg.log('║', '√', `${Dump.rev(r)} (${s}) is ${r.prev.revision === exports.ROOT_REV ? 'constructed' : `applied on top of ${Dump.rev(r.prev.revision)}`}`);
206
+ Dbg_1.Dbg.log('║', '√', `${Dump.rev2(h, r.snapshot)} (${s}) is ${r.prev.revision === exports.ROOT_REV ? 'constructed' : `applied on top of ${Dump.rev2(h, r.prev.revision.snapshot)}`}`);
204
207
  });
205
208
  }
206
209
  if (Dbg_1.Dbg.trace.transaction)
@@ -208,6 +211,7 @@ class Snapshot {
208
211
  }
209
212
  if (!error)
210
213
  Snapshot.propagateAllChangesThroughSubscriptions(this);
214
+ return this.reactions;
211
215
  }
212
216
  static sealObjectRevision(h, r) {
213
217
  if (!r.changes.has(Data_1.Meta.Disposed))
@@ -228,17 +232,9 @@ class Snapshot {
228
232
  }
229
233
  }
230
234
  }
231
- collectGarbage() {
232
- if (Dbg_1.Dbg.isOn) {
233
- Utils_1.Utils.freezeMap(this.changeset);
234
- Object.freeze(this.reactions);
235
- Object.freeze(this);
236
- }
237
- this.triggerGarbageCollection();
238
- }
239
235
  static freezeObjectRevision(r) {
240
236
  Object.freeze(r.data);
241
- Utils_1.Utils.freezeMap(r.changes);
237
+ Utils_1.Utils.freezeSet(r.changes);
242
238
  Utils_1.Utils.freezeMap(r.conflicts);
243
239
  return r;
244
240
  }
@@ -267,23 +263,25 @@ class Snapshot {
267
263
  Dbg_1.Dbg.log('', '[G]', `Dismiss history below v${this.stamp}t${this.id} (${this.hint})`);
268
264
  this.changeset.forEach((r, h) => {
269
265
  if (Dbg_1.Dbg.isOn && Dbg_1.Dbg.trace.gc && r.prev.revision !== exports.ROOT_REV)
270
- Dbg_1.Dbg.log(' ', ' ', `${Dump.rev(r.prev.revision)} is ready for GC because overwritten by ${Dump.rev(r)}`);
266
+ Dbg_1.Dbg.log(' ', ' ', `${Dump.rev2(h, r.prev.revision.snapshot)} is ready for GC because overwritten by ${Dump.rev2(h, r.snapshot)}`);
271
267
  if (Snapshot.garbageCollectionSummaryInterval < Number.MAX_SAFE_INTEGER) {
272
- if (r.prev.revision !== exports.ROOT_REV) {
268
+ if (r.prev.revision !== exports.ROOT_REV)
273
269
  Snapshot.totalObjectRevisionCount--;
274
- }
275
- if (r.changes.has(Data_1.Meta.Disposed)) {
270
+ if (r.changes.has(Data_1.Meta.Disposed))
276
271
  Snapshot.totalObjectHolderCount--;
277
- }
278
272
  }
279
273
  r.prev.revision = exports.ROOT_REV;
280
274
  });
275
+ this.changeset = EMPTY_MAP;
276
+ this.reactions = EMPTY_ARRAY;
277
+ if (Dbg_1.Dbg.isOn)
278
+ Object.freeze(this);
281
279
  }
282
280
  static _init() {
283
281
  const root = exports.ROOT_REV.snapshot;
284
282
  root.acquire(root);
285
283
  root.applyOrDiscard();
286
- root.collectGarbage();
284
+ root.triggerGarbageCollection();
287
285
  Snapshot.freezeObjectRevision(exports.ROOT_REV);
288
286
  Snapshot.idGen = 100;
289
287
  Snapshot.stampGen = 101;
@@ -309,7 +307,7 @@ Snapshot.markEdited = Utils_1.UNDEF;
309
307
  Snapshot.isConflicting = Utils_1.UNDEF;
310
308
  Snapshot.propagateAllChangesThroughSubscriptions = (snapshot) => { };
311
309
  Snapshot.revokeAllSubscriptions = (snapshot) => { };
312
- Snapshot.enqueueDetectedReactions = (snapshot) => { };
310
+ Snapshot.enqueueReactionsToRun = (reactions) => { };
313
311
  class Dump {
314
312
  static obj(h, m, stamp, op, xop, typeless) {
315
313
  const member = m !== undefined ? `.${m.toString()}` : '';
@@ -317,6 +315,9 @@ class Dump {
317
315
  ? `root${member}`
318
316
  : stamp === undefined ? `${h.hint}${member} #${h.id}` : `${h.hint}${member} #${h.id}t${op}v${stamp}${xop !== undefined && xop !== 0 ? `t${xop}` : ''}`;
319
317
  }
318
+ static rev2(h, s, m, value) {
319
+ return Dump.obj(h, m, s.timestamp, s.id, value === null || value === void 0 ? void 0 : value.selfSnapshotId);
320
+ }
320
321
  static rev(r, m) {
321
322
  const h = Data_1.Meta.get(r.data, Data_1.Meta.Holder);
322
323
  const value = m !== undefined ? r.data[m] : undefined;
@@ -228,9 +228,9 @@ class TransactionImpl extends Transaction {
228
228
  finally {
229
229
  this.pending--;
230
230
  if (this.sealed && this.pending === 0) {
231
- this.applyOrDiscard();
231
+ const reactions = this.applyOrDiscard();
232
232
  TransactionImpl.curr = outer;
233
- TransactionImpl.standalone(Snapshot_1.Snapshot.enqueueDetectedReactions, this.snapshot);
233
+ TransactionImpl.standalone(Snapshot_1.Snapshot.enqueueReactionsToRun, reactions);
234
234
  }
235
235
  else
236
236
  TransactionImpl.curr = outer;
@@ -259,11 +259,12 @@ class TransactionImpl extends Transaction {
259
259
  throw (0, Dbg_1.error)(`T${this.id}[${this.hint}] conflicts with: ${Snapshot_1.Dump.conflicts(conflicts)}`, undefined);
260
260
  }
261
261
  applyOrDiscard() {
262
+ let reactions;
262
263
  try {
263
264
  if (Dbg_1.Dbg.isOn && Dbg_1.Dbg.trace.change)
264
265
  Dbg_1.Dbg.log('╠═', '', '', undefined, 'changes');
265
- this.snapshot.applyOrDiscard(this.canceled);
266
- this.snapshot.collectGarbage();
266
+ reactions = this.snapshot.applyOrDiscard(this.canceled);
267
+ this.snapshot.triggerGarbageCollection();
267
268
  if (this.promise) {
268
269
  if (this.canceled && !this.after)
269
270
  this.reject(this.canceled);
@@ -277,6 +278,7 @@ class TransactionImpl extends Transaction {
277
278
  (0, Dbg_1.fatal)(e);
278
279
  throw e;
279
280
  }
281
+ return reactions;
280
282
  }
281
283
  acquirePromise() {
282
284
  if (!this.promise) {
@@ -1,7 +1,7 @@
1
1
  export declare type F<T> = (...args: any[]) => T;
2
2
  export declare class Utils {
3
- static freezeSet<T>(obj?: Set<T>): void;
4
- static freezeMap<K, V>(obj?: Map<K, V>): void;
3
+ static freezeSet<T>(obj?: Set<T>): Set<T> | undefined;
4
+ static freezeMap<K, V>(obj?: Map<K, V>): Map<K, V> | undefined;
5
5
  static copyAllMembers(source: any, target: any): any;
6
6
  }
7
7
  export declare function UNDEF(...args: any[]): never;
@@ -19,6 +19,7 @@ class Utils {
19
19
  Object.defineProperty(obj, 'clear', pd);
20
20
  Object.freeze(obj);
21
21
  }
22
+ return obj;
22
23
  }
23
24
  static freezeMap(obj) {
24
25
  if (obj instanceof Map) {
@@ -28,6 +29,7 @@ class Utils {
28
29
  Object.defineProperty(obj, 'clear', pd);
29
30
  Object.freeze(obj);
30
31
  }
32
+ return obj;
31
33
  }
32
34
  static copyAllMembers(source, target) {
33
35
  for (const m of Object.getOwnPropertyNames(source))
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "reactronic",
3
- "version": "0.21.600",
3
+ "version": "0.21.604",
4
4
  "description": "Reactronic - Transactional Reactive State Management",
5
5
  "main": "build/dist/source/api.js",
6
6
  "types": "build/dist/source/api.d.ts",