reactronic 0.21.520 → 0.21.524

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -300,6 +300,7 @@ function unobservable(proto, prop) // field only
300
300
  function transaction(proto, prop, pd) // method only
301
301
  function reaction(proto, prop, pd) // method only
302
302
  function cached(proto, prop, pd) // method only
303
+ function options(value: Partial<MemberOptions>): F<any>
303
304
 
304
305
  function noSideEffects(value: boolean) // transaction & cached & reaction
305
306
  function sensitiveArgs(value: boolean) // cached & reaction
@@ -324,6 +325,19 @@ interface Options {
324
325
  readonly trace?: Partial<TraceOptions>
325
326
  }
326
327
 
328
+ export interface MemberOptions {
329
+ readonly kind: Kind
330
+ readonly standalone: StandaloneMode
331
+ readonly order: number
332
+ readonly noSideEffects: boolean
333
+ readonly sensitiveArgs: boolean
334
+ readonly throttling: number // milliseconds, -1 is immediately, Number.MAX_SAFE_INTEGER is never
335
+ readonly reentrance: Reentrance
336
+ readonly journal: TransactionJournal | undefined
337
+ readonly monitor: Monitor | null
338
+ readonly trace?: Partial<TraceOptions>
339
+ }
340
+
327
341
  enum Kind {
328
342
  Plain = 0,
329
343
  Transaction = 1,
@@ -430,7 +444,7 @@ abstract class Controller<T> {
430
444
  class Reactronic {
431
445
  static why(short: boolean = false): string
432
446
  static getMethodCache<T>(method: F<T>): Cache<T>
433
- static configureCurrentMethod(options: Partial<Options>): Options
447
+ static configureCurrentOperation(options: Partial<Options>): Options
434
448
  // static configureObject<T extends object>(obj: T, options: Partial<ObjectOptions>): void
435
449
  // static assign<T, P extends keyof T>(obj: T, prop: P, value: T[P], sensitivity: Sensitivity)
436
450
  static takeSnapshot<T>(obj: T): T
@@ -1,16 +1,18 @@
1
1
  import { TraceOptions } from './Trace';
2
+ import { StandaloneMode } from './impl/Data';
2
3
  export { TraceOptions, ProfilingOptions, TraceLevel } from './Trace';
3
4
  import { TransactionJournal } from './impl/TransactionJournal';
4
5
  import { Monitor } from './impl/Monitor';
5
6
  export interface SnapshotOptions {
6
7
  readonly hint?: string;
7
- readonly standalone?: boolean;
8
+ readonly standalone?: StandaloneMode;
8
9
  readonly journal?: TransactionJournal;
9
10
  readonly trace?: Partial<TraceOptions>;
10
11
  readonly token?: any;
11
12
  }
12
13
  export interface MemberOptions {
13
14
  readonly kind: Kind;
15
+ readonly standalone: StandaloneMode;
14
16
  readonly order: number;
15
17
  readonly noSideEffects: boolean;
16
18
  readonly sensitiveArgs: boolean;
@@ -5,7 +5,7 @@ export declare class Reactronic {
5
5
  static why(brief?: boolean): string;
6
6
  static getController<T>(method: F<T>): Controller<T>;
7
7
  static pullLastResult<T>(method: F<Promise<T>>, args?: any[]): T | undefined;
8
- static configureCurrentMethod(options: Partial<MemberOptions>): MemberOptions;
8
+ static configureCurrentOperation(options: Partial<MemberOptions>): MemberOptions;
9
9
  static takeSnapshot<T>(obj: T): T;
10
10
  static dispose(obj: any): void;
11
11
  static get reactionsAutoStartDisabled(): boolean;
@@ -12,7 +12,7 @@ class Reactronic {
12
12
  static why(brief = false) { return brief ? Operation_1.OperationController.briefWhy() : Operation_1.OperationController.why(); }
13
13
  static getController(method) { return Operation_1.OperationController.of(method); }
14
14
  static pullLastResult(method, args) { return Reactronic.getController(method).pullLastResult(args); }
15
- static configureCurrentMethod(options) { return Operation_1.OperationController.configureImpl(undefined, options); }
15
+ static configureCurrentOperation(options) { return Operation_1.OperationController.configureImpl(undefined, options); }
16
16
  static takeSnapshot(obj) { return Snapshot_1.Snapshot.takeSnapshot(obj); }
17
17
  static dispose(obj) { Snapshot_1.Snapshot.dispose(obj); }
18
18
  static get reactionsAutoStartDisabled() { return Hooks_1.Hooks.reactionsAutoStartDisabled; }
@@ -23,7 +23,7 @@ export interface ProfilingOptions {
23
23
  garbageCollectionSummaryInterval: number;
24
24
  }
25
25
  export declare const TraceLevel: {
26
- Error: TraceOptions;
26
+ ErrorsOnly: TraceOptions;
27
27
  Transactions: TraceOptions;
28
28
  Operations: TraceOptions;
29
29
  Debug: TraceOptions;
@@ -2,7 +2,7 @@
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.TraceLevel = void 0;
4
4
  exports.TraceLevel = {
5
- Error: {
5
+ ErrorsOnly: {
6
6
  silent: false,
7
7
  transaction: false,
8
8
  operation: false,
@@ -1,4 +1,4 @@
1
- export { all, sleep } from './util/Utils';
1
+ export { all, pause } from './util/Utils';
2
2
  export { SealedArray } from './util/SealedArray';
3
3
  export { SealedMap } from './util/SealedMap';
4
4
  export { SealedSet } from './util/SealedSet';
@@ -1,9 +1,9 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.options = exports.cached = exports.reaction = exports.transaction = exports.unobservable = exports.sensitive = exports.standalone = exports.nonreactive = exports.Reactronic = exports.TransactionJournal = exports.Monitor = exports.Transaction = exports.Snapshot = exports.ObservableObject = exports.ToggleRef = exports.Ref = exports.Controller = exports.TraceLevel = exports.Reentrance = exports.Kind = exports.SealedSet = exports.SealedMap = exports.SealedArray = exports.sleep = exports.all = void 0;
3
+ exports.options = exports.cached = exports.reaction = exports.transaction = exports.unobservable = exports.sensitive = exports.standalone = exports.nonreactive = exports.Reactronic = exports.TransactionJournal = exports.Monitor = exports.Transaction = exports.Snapshot = exports.ObservableObject = exports.ToggleRef = exports.Ref = exports.Controller = exports.TraceLevel = exports.Reentrance = exports.Kind = exports.SealedSet = exports.SealedMap = exports.SealedArray = exports.pause = exports.all = void 0;
4
4
  var Utils_1 = require("./util/Utils");
5
5
  Object.defineProperty(exports, "all", { enumerable: true, get: function () { return Utils_1.all; } });
6
- Object.defineProperty(exports, "sleep", { enumerable: true, get: function () { return Utils_1.sleep; } });
6
+ Object.defineProperty(exports, "pause", { enumerable: true, get: function () { return Utils_1.pause; } });
7
7
  var SealedArray_1 = require("./util/SealedArray");
8
8
  Object.defineProperty(exports, "SealedArray", { enumerable: true, get: function () { return SealedArray_1.SealedArray; } });
9
9
  var SealedMap_1 = require("./util/SealedMap");
@@ -9,8 +9,10 @@ export declare class Observable {
9
9
  value: any;
10
10
  observers?: Set<Observer>;
11
11
  get isOperation(): boolean;
12
+ get selfSnapshotId(): number | undefined;
12
13
  constructor(value: any);
13
14
  }
15
+ export declare type StandaloneMode = boolean | 'isolated';
14
16
  export interface Observer {
15
17
  readonly order: number;
16
18
  readonly observables: Map<Observable, MemberInfo> | undefined;
@@ -8,6 +8,7 @@ Object.defineProperty(exports, "Meta", { enumerable: true, get: function () { re
8
8
  class Observable {
9
9
  constructor(value) { this.value = value; }
10
10
  get isOperation() { return false; }
11
+ get selfSnapshotId() { return 0; }
11
12
  }
12
13
  exports.Observable = Observable;
13
14
  class ObjectRevision {
@@ -1,7 +1,7 @@
1
1
  import { F } from '../util/Utils';
2
2
  import { MemberOptions, Kind, Reentrance } from '../Options';
3
3
  import { TraceOptions, ProfilingOptions } from '../Trace';
4
- import { MemberName, ObjectHolder } from './Data';
4
+ import { MemberName, ObjectHolder, StandaloneMode } from './Data';
5
5
  import { TransactionJournal } from './TransactionJournal';
6
6
  import { Monitor } from './Monitor';
7
7
  export declare abstract class ObservableObject {
@@ -12,6 +12,7 @@ export declare class OptionsImpl implements MemberOptions {
12
12
  readonly getter: Function;
13
13
  readonly setter: Function;
14
14
  readonly kind: Kind;
15
+ readonly standalone: StandaloneMode;
15
16
  readonly order: number;
16
17
  readonly noSideEffects: boolean;
17
18
  readonly sensitiveArgs: boolean;
@@ -37,10 +38,10 @@ export declare class Hooks implements ProxyHandler<ObjectHolder> {
37
38
  getOwnPropertyDescriptor(h: ObjectHolder, m: MemberName): PropertyDescriptor | undefined;
38
39
  ownKeys(h: ObjectHolder): Array<string | symbol>;
39
40
  static decorateData(observable: boolean, proto: any, m: MemberName): any;
40
- static decorateOperation(implicit: boolean, decorator: Function, options: Partial<MemberOptions>, proto: any, member: MemberName, pd: PropertyDescriptor): any;
41
+ static decorateOperation(implicit: boolean, decorator: Function, options: Partial<MemberOptions>, proto: any, member: MemberName, pd: PropertyDescriptor | undefined): any;
41
42
  static decorateOperationParametrized(decorator: Function, options: Partial<MemberOptions>): F<any>;
42
43
  static acquireObjectHolder(obj: any): ObjectHolder;
43
- static createObjectHolder(unobservable: any, blank: any, hint: string): ObjectHolder;
44
+ static createObjectHolder(proto: any, unobservable: any, blank: any, hint: string): ObjectHolder;
44
45
  static setProfilingMode(enabled: boolean, options?: Partial<ProfilingOptions>): void;
45
46
  static sensitive<T>(sensitivity: boolean, func: F<T>, ...args: any[]): T;
46
47
  static setHint<T>(obj: T, hint: string | undefined): T;
@@ -10,12 +10,7 @@ class ObservableObject {
10
10
  constructor() {
11
11
  const proto = new.target.prototype;
12
12
  const initial = Data_1.Meta.getFrom(proto, Data_1.Meta.Initial);
13
- const h = Hooks.createObjectHolder(this, initial, new.target.name);
14
- if (!Hooks.reactionsAutoStartDisabled) {
15
- const reactions = Data_1.Meta.getFrom(proto, Data_1.Meta.Reactions);
16
- for (const member in reactions)
17
- h.proxy[member][Data_1.Meta.Controller].markObsolete();
18
- }
13
+ const h = Hooks.createObjectHolder(proto, this, initial, new.target.name);
19
14
  return h.proxy;
20
15
  }
21
16
  [Symbol.toStringTag]() {
@@ -26,6 +21,7 @@ class ObservableObject {
26
21
  exports.ObservableObject = ObservableObject;
27
22
  const DEFAULT_OPTIONS = Object.freeze({
28
23
  kind: Options_1.Kind.Plain,
24
+ standalone: false,
29
25
  order: 0,
30
26
  noSideEffects: false,
31
27
  sensitiveArgs: false,
@@ -40,6 +36,7 @@ class OptionsImpl {
40
36
  this.getter = getter !== undefined ? getter : existing.getter;
41
37
  this.setter = setter !== undefined ? setter : existing.setter;
42
38
  this.kind = merge(DEFAULT_OPTIONS.kind, existing.kind, patch.kind, implicit);
39
+ this.standalone = merge(DEFAULT_OPTIONS.standalone, existing.standalone, patch.standalone, implicit);
43
40
  this.order = merge(DEFAULT_OPTIONS.order, existing.order, patch.order, implicit);
44
41
  this.noSideEffects = merge(DEFAULT_OPTIONS.noSideEffects, existing.noSideEffects, patch.noSideEffects, implicit);
45
42
  this.sensitiveArgs = merge(DEFAULT_OPTIONS.sensitiveArgs, existing.sensitiveArgs, patch.sensitiveArgs, implicit);
@@ -81,13 +78,14 @@ class Hooks {
81
78
  let curr = r.data[m];
82
79
  if (curr !== undefined || (r.prev.revision.snapshot === Snapshot_1.ROOT_REV.snapshot && (m in h.unobservable) === false)) {
83
80
  if (curr === undefined || curr.value !== value || Hooks.sensitivity) {
81
+ const old = curr === null || curr === void 0 ? void 0 : curr.value;
84
82
  if (r.prev.revision.data[m] === curr) {
85
83
  curr = r.data[m] = new Data_1.Observable(value);
86
- Snapshot_1.Snapshot.markEdited(value, true, r, m, h);
84
+ Snapshot_1.Snapshot.markEdited(old, value, true, r, m, h);
87
85
  }
88
86
  else {
89
87
  curr.value = value;
90
- Snapshot_1.Snapshot.markEdited(value, true, r, m, h);
88
+ Snapshot_1.Snapshot.markEdited(old, value, true, r, m, h);
91
89
  }
92
90
  }
93
91
  }
@@ -184,10 +182,13 @@ class Hooks {
184
182
  }
185
183
  return h;
186
184
  }
187
- static createObjectHolder(unobservable, blank, hint) {
185
+ static createObjectHolder(proto, unobservable, blank, hint) {
188
186
  const ctx = Snapshot_1.Snapshot.edit();
189
187
  const h = new Data_1.ObjectHolder(unobservable, undefined, Hooks.proxy, Snapshot_1.ROOT_REV, hint);
190
188
  ctx.getEditableRevision(h, Data_1.Meta.Holder, blank);
189
+ if (!Hooks.reactionsAutoStartDisabled)
190
+ for (const m in Data_1.Meta.getFrom(proto, Data_1.Meta.Reactions))
191
+ h.proxy[m][Data_1.Meta.Controller].markObsolete();
191
192
  return h;
192
193
  }
193
194
  static setProfilingMode(enabled, options) {
@@ -53,7 +53,7 @@ class MonitorImpl extends Monitor {
53
53
  static activate(mon, delay) {
54
54
  if (delay >= 0) {
55
55
  if (mon.internals.activationTimeout === undefined)
56
- mon.internals.activationTimeout = setTimeout(() => Transaction_1.Transaction.runAs({ hint: 'Monitor.activate', standalone: true }, MonitorImpl.activate, mon, -1), delay);
56
+ mon.internals.activationTimeout = setTimeout(() => Transaction_1.Transaction.runAs({ hint: 'Monitor.activate', standalone: 'isolated' }, MonitorImpl.activate, mon, -1), delay);
57
57
  }
58
58
  else if (mon.counter > 0)
59
59
  mon.isActive = true;
@@ -61,7 +61,7 @@ class MonitorImpl extends Monitor {
61
61
  static deactivate(mon, delay) {
62
62
  if (delay >= 0) {
63
63
  clearTimeout(mon.internals.deactivationTimeout);
64
- mon.internals.deactivationTimeout = setTimeout(() => Transaction_1.Transaction.runAs({ hint: 'Monitor.deactivate', standalone: true }, MonitorImpl.deactivate, mon, -1), delay);
64
+ mon.internals.deactivationTimeout = setTimeout(() => Transaction_1.Transaction.runAs({ hint: 'Monitor.deactivate', standalone: 'isolated' }, MonitorImpl.deactivate, mon, -1), delay);
65
65
  }
66
66
  else if (mon.counter <= 0) {
67
67
  mon.isActive = false;
@@ -51,8 +51,10 @@ declare class Operation extends Observable implements Observer {
51
51
  successor: Operation | undefined;
52
52
  constructor(controller: OperationController, revision: ObjectRevision, prev: Operation | OptionsImpl);
53
53
  get isOperation(): boolean;
54
+ get selfSnapshotId(): number;
54
55
  hint(): string;
55
56
  get order(): number;
57
+ get ['#this'](): string;
56
58
  why(): string;
57
59
  briefWhy(): string;
58
60
  dependencies(): string[];
@@ -77,7 +79,7 @@ declare class Operation extends Observable implements Observer {
77
79
  private static propagateMemberChangeThroughSubscriptions;
78
80
  private unsubscribeFromAllObservables;
79
81
  private subscribeTo;
80
- private static isValid;
82
+ private static canSubscribe;
81
83
  private static createControllerAndGetHook;
82
84
  private static rememberOperationOptions;
83
85
  static init(): void;
@@ -30,6 +30,7 @@ class OperationController extends Controller_1.Controller {
30
30
  markObsolete() { Transaction_1.Transaction.runAs({ hint: Dbg_1.Dbg.isOn ? `markObsolete(${Snapshot_1.Dump.obj(this.ownHolder, this.memberName)})` : 'markObsolete()' }, OperationController.markObsolete, this); }
31
31
  pullLastResult(args) { return this.useOrRun(true, args).value; }
32
32
  useOrRun(weak, args) {
33
+ var _a;
33
34
  let oc = this.peek(args);
34
35
  const ctx = oc.snapshot;
35
36
  const op = oc.operation;
@@ -37,7 +38,9 @@ class OperationController extends Controller_1.Controller {
37
38
  if (!oc.isUpToDate && oc.revision.data[Data_1.Meta.Disposed] === undefined
38
39
  && (!weak || op.cause === ROOT_TRIGGER || !op.successor ||
39
40
  op.successor.transaction.isFinished)) {
40
- const standalone = weak || opts.kind === Options_1.Kind.Reaction ||
41
+ const outerOpts = (_a = Operation.current) === null || _a === void 0 ? void 0 : _a.options;
42
+ const standalone = weak || opts.standalone || opts.kind === Options_1.Kind.Reaction ||
43
+ (opts.kind === Options_1.Kind.Transaction && outerOpts && (outerOpts.noSideEffects || outerOpts.kind === Options_1.Kind.Cache)) ||
41
44
  (opts.kind === Options_1.Kind.Cache && (oc.revision.snapshot.sealed ||
42
45
  oc.revision.prev.revision !== Snapshot_1.ROOT_REV));
43
46
  const token = opts.noSideEffects ? this : undefined;
@@ -69,7 +72,7 @@ class OperationController extends Controller_1.Controller {
69
72
  throw (0, Dbg_1.misuse)('a method is expected with reactronic decorator');
70
73
  op.options = new Hooks_1.OptionsImpl(op.options.getter, op.options.setter, op.options, options, false);
71
74
  if (Dbg_1.Dbg.isOn && Dbg_1.Dbg.trace.write)
72
- Dbg_1.Dbg.log('║', ' ✎', `${op.hint()}.options = ...`);
75
+ Dbg_1.Dbg.log('║', ' ✎', `${op.hint()}.options are changed`);
73
76
  return op.options;
74
77
  }
75
78
  static runWithin(op, func, ...args) {
@@ -125,9 +128,10 @@ class OperationController extends Controller_1.Controller {
125
128
  let op = this.peekFromRevision(r);
126
129
  if (op.revision !== r) {
127
130
  const op2 = new Operation(this, r, op);
128
- op = r.data[m] = op2.reenterOver(op);
131
+ r.data[m] = op2.reenterOver(op);
129
132
  ctx.bumpBy(r.prev.revision.snapshot.timestamp);
130
- Snapshot_1.Snapshot.markEdited(op, true, r, m, h);
133
+ Snapshot_1.Snapshot.markEdited(op, op2, true, r, m, h);
134
+ op = op2;
131
135
  }
132
136
  return { operation: op, isUpToDate: true, snapshot: ctx, revision: r };
133
137
  }
@@ -143,9 +147,11 @@ class OperationController extends Controller_1.Controller {
143
147
  let op2 = r2.data[m];
144
148
  if (op2.controller !== this) {
145
149
  r2 = Snapshot_1.Snapshot.edit().getEditableRevision(h, m, Data_1.Meta.Holder, this);
146
- op2 = r2.data[m] = new Operation(this, r2, op2);
147
- op2.cause = ROOT_TRIGGER;
148
- Snapshot_1.Snapshot.markEdited(op2, true, r2, m, h);
150
+ const t = new Operation(this, r2, op2);
151
+ t.cause = ROOT_TRIGGER;
152
+ r2.data[m] = t;
153
+ Snapshot_1.Snapshot.markEdited(op2, t, true, r2, m, h);
154
+ op2 = t;
149
155
  }
150
156
  return op2;
151
157
  });
@@ -159,7 +165,7 @@ class OperationController extends Controller_1.Controller {
159
165
  const result = Transaction_1.Transaction.runAs(opts, (argsx) => {
160
166
  if (!oc.operation.transaction.isCanceled) {
161
167
  oc = this.edit();
162
- if (Dbg_1.Dbg.isOn && (Dbg_1.Dbg.trace.transaction || Dbg_1.Dbg.trace.operation || Dbg_1.Dbg.trace.obsolete))
168
+ if (Dbg_1.Dbg.isOn && Dbg_1.Dbg.trace.operation)
163
169
  Dbg_1.Dbg.log('║', ' 𝑓', `${oc.operation.why()}`);
164
170
  oc.operation.run(this.ownHolder.proxy, argsx);
165
171
  }
@@ -167,7 +173,7 @@ class OperationController extends Controller_1.Controller {
167
173
  oc = this.peek(argsx);
168
174
  if (oc.operation.options.kind === Options_1.Kind.Transaction || !oc.isUpToDate) {
169
175
  oc = this.edit();
170
- if (Dbg_1.Dbg.isOn && (Dbg_1.Dbg.trace.transaction || Dbg_1.Dbg.trace.operation || Dbg_1.Dbg.trace.obsolete))
176
+ if (Dbg_1.Dbg.isOn && Dbg_1.Dbg.trace.operation)
171
177
  Dbg_1.Dbg.log('║', ' 𝑓', `${oc.operation.why()}`);
172
178
  oc.operation.run(this.ownHolder.proxy, argsx);
173
179
  }
@@ -208,8 +214,12 @@ class Operation extends Data_1.Observable {
208
214
  this.successor = undefined;
209
215
  }
210
216
  get isOperation() { return true; }
217
+ get selfSnapshotId() { return this.revision.snapshot.id; }
211
218
  hint() { return `${Snapshot_1.Dump.rev(this.revision, this.controller.memberName)}`; }
212
219
  get order() { return this.options.order; }
220
+ get ['#this']() {
221
+ return `Operation: ${this.why()}`;
222
+ }
213
223
  why() {
214
224
  let ms = Date.now();
215
225
  const prev = this.revision.prev.revision.data[this.controller.memberName];
@@ -221,7 +231,7 @@ class Operation extends Data_1.Observable {
221
231
  else if (this.controller.options.kind === Options_1.Kind.Transaction)
222
232
  trigger = ' << operation';
223
233
  else
224
- trigger = ` << called within ${this.revision.snapshot.hint}`;
234
+ trigger = ` << T${this.revision.snapshot.id}[${this.revision.snapshot.hint}]`;
225
235
  return `${this.hint()}${trigger} (${ms !== Infinity ? `${ms}ms since previous run` : 'initial run'})`;
226
236
  }
227
237
  briefWhy() {
@@ -366,18 +376,18 @@ class Operation extends Data_1.Observable {
366
376
  if (this.result instanceof Promise) {
367
377
  this.result = this.result.then(value => {
368
378
  this.value = value;
369
- this.leave(false, ' ', '- finished ', ' OK ──┘');
379
+ this.leave(false, ' ', '- finished ', ' OK ──┘');
370
380
  return value;
371
381
  }, error => {
372
382
  this.error = error;
373
- this.leave(false, ' ', '- finished ', 'ERR ──┘');
383
+ this.leave(false, ' ', '- finished ', 'ERR ──┘');
374
384
  throw error;
375
385
  });
376
386
  if (Dbg_1.Dbg.isOn) {
377
387
  if (Dbg_1.Dbg.trace.operation)
378
388
  Dbg_1.Dbg.log('║', '_/', `${this.hint()} - leave... `, 0, 'ASYNC ──┐');
379
389
  else if (Dbg_1.Dbg.trace.transaction)
380
- Dbg_1.Dbg.log('║', ' ', `${this.hint()}... `, 0, 'ASYNC');
390
+ Dbg_1.Dbg.log('║', ' ', `${this.why()} ...`, 0, 'ASYNC');
381
391
  }
382
392
  }
383
393
  else {
@@ -398,8 +408,8 @@ class Operation extends Data_1.Observable {
398
408
  monitorEnter(mon) {
399
409
  const options = {
400
410
  hint: 'Monitor.enter',
401
- standalone: true,
402
- trace: Dbg_1.Dbg.isOn && Dbg_1.Dbg.trace.monitor ? undefined : Dbg_1.Dbg.global,
411
+ standalone: 'isolated',
412
+ trace: Dbg_1.Dbg.isOn && Dbg_1.Dbg.trace.monitor ? undefined : Dbg_1.Dbg.global
403
413
  };
404
414
  OperationController.runWithin(undefined, Transaction_1.Transaction.runAs, options, Monitor_1.MonitorImpl.enter, mon, this.transaction);
405
415
  }
@@ -408,8 +418,8 @@ class Operation extends Data_1.Observable {
408
418
  const leave = () => {
409
419
  const options = {
410
420
  hint: 'Monitor.leave',
411
- standalone: true,
412
- trace: Dbg_1.Dbg.isOn && Dbg_1.Dbg.trace.monitor ? undefined : Dbg_1.Dbg.DefaultLevel,
421
+ standalone: 'isolated',
422
+ trace: Dbg_1.Dbg.isOn && Dbg_1.Dbg.trace.monitor ? undefined : Dbg_1.Dbg.DefaultLevel
413
423
  };
414
424
  OperationController.runWithin(undefined, Transaction_1.Transaction.runAs, options, Monitor_1.MonitorImpl.leave, mon, this.transaction);
415
425
  };
@@ -440,10 +450,10 @@ class Operation extends Data_1.Observable {
440
450
  }
441
451
  }
442
452
  }
443
- static markEdited(value, edited, r, m, h) {
453
+ static markEdited(oldValue, newValue, edited, r, m, h) {
444
454
  edited ? r.changes.set(m, Operation.current) : r.changes.delete(m);
445
455
  if (Dbg_1.Dbg.isOn && Dbg_1.Dbg.trace.write)
446
- edited ? Dbg_1.Dbg.log('║', ' ✎', `${Snapshot_1.Dump.rev(r, m)} = ${valueHint(value)}`) : Dbg_1.Dbg.log('║', ' ✎', `${Snapshot_1.Dump.rev(r, m)} = ${valueHint(value)}`, undefined, ' (same as previous)');
456
+ 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)');
447
457
  }
448
458
  static isConflicting(oldValue, newValue) {
449
459
  let result = oldValue !== newValue;
@@ -515,29 +525,33 @@ class Operation extends Data_1.Observable {
515
525
  this.observables = undefined;
516
526
  }
517
527
  subscribeTo(observable, r, m, h, timestamp) {
518
- var _a, _b;
519
- const isValid = Operation.isValid(observable, r, m, h, timestamp);
520
- if (isValid) {
528
+ var _a, _b, _c;
529
+ const ok = Operation.canSubscribe(observable, r, m, h, timestamp);
530
+ if (ok) {
521
531
  let times = 0;
522
532
  if (Hooks_1.Hooks.repetitiveUsageWarningThreshold < Number.MAX_SAFE_INTEGER) {
523
533
  const existing = this.observables.get(observable);
524
534
  times = existing ? existing.usageCount + 1 : 1;
525
535
  }
526
- if (!observable.observers)
527
- observable.observers = new Set();
528
- const info = { revision: r, memberName: m, usageCount: times };
529
- observable.observers.add(this);
530
- this.observables.set(observable, info);
531
- if (Dbg_1.Dbg.isOn && (Dbg_1.Dbg.trace.read || ((_a = this.options.trace) === null || _a === void 0 ? void 0 : _a.read)))
532
- Dbg_1.Dbg.log('║', ' ∞ ', `${this.hint()} is subscribed to ${Snapshot_1.Dump.rev(r, m)}${info.usageCount > 1 ? ` (${info.usageCount} times)` : ''}`);
536
+ if (this.observables !== undefined) {
537
+ if (!observable.observers)
538
+ observable.observers = new Set();
539
+ const info = { revision: r, memberName: m, usageCount: times };
540
+ observable.observers.add(this);
541
+ this.observables.set(observable, info);
542
+ if (Dbg_1.Dbg.isOn && (Dbg_1.Dbg.trace.read || ((_a = this.options.trace) === null || _a === void 0 ? void 0 : _a.read)))
543
+ Dbg_1.Dbg.log('║', ' ∞ ', `${this.hint()} is subscribed to ${Snapshot_1.Dump.rev(r, m)}${info.usageCount > 1 ? ` (${info.usageCount} times)` : ''}`);
544
+ }
545
+ else if (Dbg_1.Dbg.isOn && (Dbg_1.Dbg.trace.read || ((_b = this.options.trace) === null || _b === void 0 ? void 0 : _b.read)))
546
+ Dbg_1.Dbg.log('║', ' x ', `${this.hint()} is obsolete and is NOT subscribed to ${Snapshot_1.Dump.rev(r, m)}`);
533
547
  }
534
548
  else {
535
- if (Dbg_1.Dbg.isOn && (Dbg_1.Dbg.trace.read || ((_b = this.options.trace) === null || _b === void 0 ? void 0 : _b.read)))
549
+ if (Dbg_1.Dbg.isOn && (Dbg_1.Dbg.trace.read || ((_c = this.options.trace) === null || _c === void 0 ? void 0 : _c.read)))
536
550
  Dbg_1.Dbg.log('║', ' x ', `${this.hint()} is NOT subscribed to already obsolete ${Snapshot_1.Dump.rev(r, m)}`);
537
551
  }
538
- return isValid;
552
+ return ok;
539
553
  }
540
- static isValid(observable, r, m, h, timestamp) {
554
+ static canSubscribe(observable, r, m, h, timestamp) {
541
555
  let result = !r.snapshot.sealed || observable === h.head.data[m];
542
556
  if (result && timestamp !== -1)
543
557
  result = !(observable instanceof Operation && timestamp >= observable.obsoleteSince);
@@ -615,7 +629,7 @@ function propagationHint(cause, full) {
615
629
  full && result.push(cause.revision.snapshot.hint);
616
630
  return result;
617
631
  }
618
- function valueHint(value) {
632
+ function valueHint(value, m) {
619
633
  let result = '';
620
634
  if (Array.isArray(value))
621
635
  result = `Array(${value.length})`;
@@ -624,13 +638,13 @@ function valueHint(value) {
624
638
  else if (value instanceof Map)
625
639
  result = `Map(${value.size})`;
626
640
  else if (value instanceof Operation)
627
- result = `<rerun over ${Snapshot_1.Dump.rev(value.revision.prev.revision)}>`;
641
+ result = `${Snapshot_1.Dump.rev(value.revision, m)}`;
628
642
  else if (value === Data_1.Meta.Disposed)
629
643
  result = '<disposed>';
630
644
  else if (value !== undefined && value !== null)
631
645
  result = value.toString().slice(0, 20);
632
646
  else
633
- result = '';
647
+ result = '';
634
648
  return result;
635
649
  }
636
650
  function getMergedTraceOptions(local) {
@@ -24,7 +24,7 @@ export declare class Snapshot implements AbstractSnapshot {
24
24
  static current: () => Snapshot;
25
25
  static edit: () => Snapshot;
26
26
  static markUsed: (observable: Observable, r: ObjectRevision, m: MemberName, h: ObjectHolder, kind: Kind, weak: boolean) => void;
27
- static markEdited: (value: any, edited: boolean, r: ObjectRevision, m: MemberName, h: ObjectHolder) => void;
27
+ static markEdited: (oldValue: any, newValue: any, edited: boolean, r: ObjectRevision, m: MemberName, h: ObjectHolder) => void;
28
28
  static isConflicting: (oldValue: any, newValue: any) => boolean;
29
29
  static propagateAllChangesThroughSubscriptions: (snapshot: Snapshot) => void;
30
30
  static revokeAllSubscriptions: (snapshot: Snapshot) => void;
@@ -49,7 +49,7 @@ export declare class Snapshot implements AbstractSnapshot {
49
49
  static _init(): void;
50
50
  }
51
51
  export declare class Dump {
52
- static obj(h: ObjectHolder | undefined, m?: MemberName | undefined, stamp?: number, op?: number, typeless?: boolean): string;
52
+ static obj(h: ObjectHolder | undefined, m?: MemberName | undefined, stamp?: number, op?: number, xop?: number, typeless?: boolean): string;
53
53
  static rev(r: ObjectRevision, m?: MemberName): string;
54
54
  static conflicts(conflicts: ObjectRevision[]): string;
55
55
  static conflictingMemberHint(m: MemberName, ours: ObjectRevision, theirs: ObjectRevision): string;
@@ -10,11 +10,11 @@ const SealedSet_1 = require("../util/SealedSet");
10
10
  const Data_1 = require("./Data");
11
11
  exports.MAX_TIMESTAMP = Number.MAX_SAFE_INTEGER;
12
12
  exports.UNDEFINED_TIMESTAMP = exports.MAX_TIMESTAMP - 1;
13
- Object.defineProperty(Data_1.ObjectHolder.prototype, '<snapshot>', {
13
+ Object.defineProperty(Data_1.ObjectHolder.prototype, '#this', {
14
14
  configurable: false, enumerable: false,
15
15
  get() {
16
16
  const result = {};
17
- const data = Snapshot.current().getCurrentRevision(this, '<snapshot>').data;
17
+ const data = Snapshot.current().getCurrentRevision(this, '#this').data;
18
18
  for (const m in data) {
19
19
  const v = data[m];
20
20
  if (v instanceof Data_1.Observable)
@@ -71,6 +71,8 @@ class Snapshot {
71
71
  this.changeset.set(h, r);
72
72
  h.editing = r;
73
73
  h.editors++;
74
+ if (Dbg_1.Dbg.isOn && Dbg_1.Dbg.trace.write)
75
+ Dbg_1.Dbg.log('║', ' ⎘', `${Dump.obj(h)} is cloned`);
74
76
  }
75
77
  }
76
78
  else
@@ -78,7 +80,7 @@ class Snapshot {
78
80
  return r;
79
81
  }
80
82
  static takeSnapshot(obj) {
81
- return obj[Data_1.Meta.Holder]['<snapshot>'];
83
+ return obj[Data_1.Meta.Holder]['#this'];
82
84
  }
83
85
  static dispose(obj) {
84
86
  const ctx = Snapshot.edit();
@@ -90,7 +92,7 @@ class Snapshot {
90
92
  const r = ctx.getEditableRevision(h, Data_1.Meta.Disposed, Data_1.Meta.Disposed);
91
93
  if (r !== exports.ROOT_REV) {
92
94
  r.data[Data_1.Meta.Disposed] = Data_1.Meta.Disposed;
93
- Snapshot.markEdited(Data_1.Meta.Disposed, true, r, Data_1.Meta.Disposed, h);
95
+ Snapshot.markEdited(Data_1.Meta.Disposed, Data_1.Meta.Disposed, true, r, Data_1.Meta.Disposed, h);
94
96
  }
95
97
  return r;
96
98
  }
@@ -132,7 +134,7 @@ class Snapshot {
132
134
  conflicts = [];
133
135
  conflicts.push(r);
134
136
  }
135
- if (Dbg_1.Dbg.isOn && Dbg_1.Dbg.trace.change)
137
+ if (Dbg_1.Dbg.isOn && Dbg_1.Dbg.trace.transaction)
136
138
  Dbg_1.Dbg.log('╠╝', '', `${Dump.rev(r)} is merged with ${Dump.rev(h.head)} among ${merged} properties with ${r.conflicts.size} conflicts.`);
137
139
  }
138
140
  });
@@ -193,7 +195,7 @@ class Snapshot {
193
195
  }
194
196
  });
195
197
  if (Dbg_1.Dbg.isOn) {
196
- if (Dbg_1.Dbg.trace.change) {
198
+ if (Dbg_1.Dbg.trace.change && !error) {
197
199
  this.changeset.forEach((r, h) => {
198
200
  const members = [];
199
201
  r.changes.forEach((o, m) => members.push(m.toString()));
@@ -308,15 +310,16 @@ Snapshot.isConflicting = Utils_1.UNDEF;
308
310
  Snapshot.propagateAllChangesThroughSubscriptions = (snapshot) => { };
309
311
  Snapshot.revokeAllSubscriptions = (snapshot) => { };
310
312
  class Dump {
311
- static obj(h, m, stamp, op, typeless) {
313
+ static obj(h, m, stamp, op, xop, typeless) {
312
314
  const member = m !== undefined ? `.${m.toString()}` : '';
313
315
  return h === undefined
314
316
  ? `root${member}`
315
- : stamp === undefined ? `${h.hint}${member} #${h.id}` : `${h.hint}${member} #${h.id}t${op}v${stamp}`;
317
+ : stamp === undefined ? `${h.hint}${member} #${h.id}` : `${h.hint}${member} #${h.id}t${op}v${stamp}${xop !== undefined && xop !== 0 ? `t${xop}` : ''}`;
316
318
  }
317
319
  static rev(r, m) {
318
320
  const h = Data_1.Meta.get(r.data, Data_1.Meta.Holder);
319
- return Dump.obj(h, m, r.snapshot.timestamp, r.snapshot.id);
321
+ const value = m !== undefined ? r.data[m] : undefined;
322
+ return Dump.obj(h, m, r.snapshot.timestamp, r.snapshot.id, value === null || value === void 0 ? void 0 : value.selfSnapshotId);
320
323
  }
321
324
  static conflicts(conflicts) {
322
325
  return conflicts.map(ours => {
@@ -328,11 +331,11 @@ class Dump {
328
331
  }).join(', ');
329
332
  }
330
333
  static conflictingMemberHint(m, ours, theirs) {
331
- return `${theirs.snapshot.hint} on ${Dump.rev(theirs, m)}`;
334
+ return `${theirs.snapshot.hint} (${Dump.rev(theirs, m)})`;
332
335
  }
333
336
  }
334
337
  exports.Dump = Dump;
335
- exports.ROOT_REV = new Data_1.ObjectRevision(new Snapshot({ hint: 'root' }), undefined, {});
338
+ exports.ROOT_REV = new Data_1.ObjectRevision(new Snapshot({ hint: 'root-rev' }), undefined, {});
336
339
  exports.DefaultSnapshotOptions = Object.freeze({
337
340
  hint: 'noname',
338
341
  standalone: false,
@@ -24,4 +24,7 @@ export declare abstract class Transaction implements Worker {
24
24
  static run<T>(func: F<T>, ...args: any[]): T;
25
25
  static runAs<T>(options: SnapshotOptions | null, func: F<T>, ...args: any[]): T;
26
26
  static standalone<T>(func: F<T>, ...args: any[]): T;
27
+ static isTimeOver(everyN?: number): boolean;
28
+ static requestMoreTime(): Promise<void>;
29
+ static get isCanceled(): boolean;
27
30
  }
@@ -22,6 +22,9 @@ class Transaction {
22
22
  static run(func, ...args) { return TransactionImpl.run(func, ...args); }
23
23
  static runAs(options, func, ...args) { return TransactionImpl.runAs(options, func, ...args); }
24
24
  static standalone(func, ...args) { return TransactionImpl.standalone(func, ...args); }
25
+ static isTimeOver(everyN = 1) { return TransactionImpl.isTimeOver(everyN); }
26
+ static requestMoreTime() { return TransactionImpl.requestMoreTime(); }
27
+ static get isCanceled() { return TransactionImpl.current.isCanceled; }
25
28
  }
26
29
  exports.Transaction = Transaction;
27
30
  class TransactionImpl extends Transaction {
@@ -139,10 +142,24 @@ class TransactionImpl extends Transaction {
139
142
  TransactionImpl.curr = outer;
140
143
  }
141
144
  }
145
+ static isTimeOver(everyN = 1) {
146
+ TransactionImpl.checkCount++;
147
+ let result = TransactionImpl.checkCount % everyN === 0;
148
+ if (result) {
149
+ const ms = performance.now() - TransactionImpl.startTime;
150
+ result = ms > TransactionImpl.timeLimit;
151
+ }
152
+ return result;
153
+ }
154
+ static requestMoreTime(sleepTime = 0) {
155
+ return (0, Utils_1.pause)(sleepTime);
156
+ }
142
157
  static acquire(options) {
143
- return (options === null || options === void 0 ? void 0 : options.standalone) || TransactionImpl.curr.isFinished
144
- ? new TransactionImpl(options)
145
- : TransactionImpl.curr;
158
+ const curr = TransactionImpl.curr;
159
+ if ((options === null || options === void 0 ? void 0 : options.standalone) || curr.isFinished || curr.options.standalone === 'isolated')
160
+ return new TransactionImpl(options);
161
+ else
162
+ return TransactionImpl.curr;
146
163
  }
147
164
  guard() {
148
165
  if (this.sealed && TransactionImpl.curr !== this)
@@ -162,7 +179,7 @@ class TransactionImpl extends Transaction {
162
179
  yield this.after.whenFinished();
163
180
  const options = {
164
181
  hint: `${this.hint} - restart after T${this.after.id}`,
165
- standalone: true,
182
+ standalone: this.options.standalone === 'isolated' ? 'isolated' : true,
166
183
  trace: this.snapshot.options.trace,
167
184
  token: this.snapshot.options.token,
168
185
  };
@@ -187,6 +204,10 @@ class TransactionImpl extends Transaction {
187
204
  let result;
188
205
  const outer = TransactionImpl.curr;
189
206
  try {
207
+ if (outer === TransactionImpl.none) {
208
+ TransactionImpl.startTime = performance.now();
209
+ TransactionImpl.checkCount = 0;
210
+ }
190
211
  TransactionImpl.curr = this;
191
212
  this.pending++;
192
213
  this.snapshot.acquire(outer.snapshot);
@@ -242,7 +263,7 @@ class TransactionImpl extends Transaction {
242
263
  applyOrDiscard() {
243
264
  try {
244
265
  if (Dbg_1.Dbg.isOn && Dbg_1.Dbg.trace.change)
245
- Dbg_1.Dbg.log('╠══', '', '', undefined, ' changes');
266
+ Dbg_1.Dbg.log('╠═', '', '', undefined, 'changes');
246
267
  this.snapshot.applyOrDiscard(this.canceled);
247
268
  this.snapshot.collectGarbage();
248
269
  if (this.promise) {
@@ -287,4 +308,7 @@ class TransactionImpl extends Transaction {
287
308
  TransactionImpl.none = new TransactionImpl({ hint: '<none>' });
288
309
  TransactionImpl.curr = TransactionImpl.none;
289
310
  TransactionImpl.inspection = false;
311
+ TransactionImpl.startTime = 0;
312
+ TransactionImpl.timeLimit = 14;
313
+ TransactionImpl.checkCount = 0;
290
314
  TransactionImpl._init();
@@ -24,7 +24,7 @@ class TransactionJournalImpl extends TransactionJournal {
24
24
  get canUndo() { return this._items.length > 0 && this._position > 0; }
25
25
  get canRedo() { return this._position < this._items.length; }
26
26
  remember(p) {
27
- Transaction_1.Transaction.runAs({ hint: 'TransactionJournal.remember', standalone: true }, () => {
27
+ Transaction_1.Transaction.runAs({ hint: 'TransactionJournal.remember', standalone: 'isolated' }, () => {
28
28
  const items = this._items = this._items.toMutable();
29
29
  if (items.length >= this._capacity)
30
30
  items.shift();
@@ -35,7 +35,7 @@ class TransactionJournalImpl extends TransactionJournal {
35
35
  });
36
36
  }
37
37
  undo(count = 1) {
38
- Transaction_1.Transaction.runAs({ hint: 'TransactionJournal.undo', standalone: true }, () => {
38
+ Transaction_1.Transaction.runAs({ hint: 'TransactionJournal.undo', standalone: 'isolated' }, () => {
39
39
  let i = this._position - 1;
40
40
  while (i >= 0 && count > 0) {
41
41
  const patch = this._items[i];
@@ -46,7 +46,7 @@ class TransactionJournalImpl extends TransactionJournal {
46
46
  });
47
47
  }
48
48
  redo(count = 1) {
49
- Transaction_1.Transaction.runAs({ hint: 'TransactionJournal.redo', standalone: true }, () => {
49
+ Transaction_1.Transaction.runAs({ hint: 'TransactionJournal.redo', standalone: 'isolated' }, () => {
50
50
  let i = this._position;
51
51
  while (i < this._items.length && count > 0) {
52
52
  const patch = this._items[i];
@@ -61,7 +61,7 @@ class TransactionJournalImpl extends TransactionJournal {
61
61
  changeset.forEach((r, h) => {
62
62
  const p = { current: {}, former: {} };
63
63
  const old = r.prev.revision !== Snapshot_1.ROOT_REV ? r.prev.revision.data : undefined;
64
- r.changes.forEach((o, m) => {
64
+ r.changes.forEach((episode, m) => {
65
65
  p.current[m] = unseal(r.data[m]);
66
66
  if (old)
67
67
  p.former[m] = unseal(old[m]);
@@ -79,14 +79,14 @@ class TransactionJournalImpl extends TransactionJournal {
79
79
  patch.objects.forEach((p, obj) => {
80
80
  const h = Data_1.Meta.get(obj, Data_1.Meta.Holder);
81
81
  const data = undo ? p.former : p.current;
82
- if (data[Data_1.Meta.Disposed] !== Data_1.Meta.Disposed) {
82
+ if (data[Data_1.Meta.Disposed] === undefined) {
83
83
  for (const m in data) {
84
84
  const value = data[m];
85
85
  const r = ctx.getEditableRevision(h, m, value);
86
86
  if (r.snapshot === ctx) {
87
87
  r.data[m] = new Data_1.Observable(value);
88
88
  const v = r.prev.revision.data[m];
89
- Snapshot_1.Snapshot.markEdited(value, v !== value, r, m, h);
89
+ Snapshot_1.Snapshot.markEdited(v, value, v !== value, r, m, h);
90
90
  }
91
91
  }
92
92
  }
@@ -6,4 +6,4 @@ export declare class Utils {
6
6
  }
7
7
  export declare function UNDEF(...args: any[]): never;
8
8
  export declare function all(promises: Array<Promise<any>>): Promise<any[]>;
9
- export declare function sleep<T>(timeout: number): Promise<T>;
9
+ export declare function pause<T>(timeout: number): Promise<T>;
@@ -9,7 +9,7 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, ge
9
9
  });
10
10
  };
11
11
  Object.defineProperty(exports, "__esModule", { value: true });
12
- exports.sleep = exports.all = exports.UNDEF = exports.Utils = void 0;
12
+ exports.pause = exports.all = exports.UNDEF = exports.Utils = void 0;
13
13
  class Utils {
14
14
  static freezeSet(obj) {
15
15
  if (obj instanceof Set) {
@@ -52,9 +52,9 @@ function all(promises) {
52
52
  });
53
53
  }
54
54
  exports.all = all;
55
- function sleep(timeout) {
55
+ function pause(timeout) {
56
56
  return new Promise(function (resolve) {
57
57
  setTimeout(resolve.bind(null, () => resolve), timeout);
58
58
  });
59
59
  }
60
- exports.sleep = sleep;
60
+ exports.pause = pause;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "reactronic",
3
- "version": "0.21.520",
3
+ "version": "0.21.524",
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",
@@ -29,10 +29,10 @@
29
29
  },
30
30
  "homepage": "https://github.com/nezaboodka/reactronic/blob/master/README.md#readme",
31
31
  "devDependencies": {
32
- "@types/node": "16.11.10",
32
+ "@types/node": "16.11.11",
33
33
  "@types/react": "17.0.37",
34
- "@typescript-eslint/eslint-plugin": "5.4.0",
35
- "@typescript-eslint/parser": "5.4.0",
34
+ "@typescript-eslint/eslint-plugin": "5.5.0",
35
+ "@typescript-eslint/parser": "5.5.0",
36
36
  "ava": "3.15.0",
37
37
  "eslint": "8.3.0",
38
38
  "nyc": "15.1.0",