reactronic 0.21.523 → 0.21.527

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,
@@ -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;
@@ -39,7 +39,7 @@ class OperationController extends Controller_1.Controller {
39
39
  && (!weak || op.cause === ROOT_TRIGGER || !op.successor ||
40
40
  op.successor.transaction.isFinished)) {
41
41
  const outerOpts = (_a = Operation.current) === null || _a === void 0 ? void 0 : _a.options;
42
- const standalone = weak || opts.kind === Options_1.Kind.Reaction ||
42
+ const standalone = weak || opts.standalone || opts.kind === Options_1.Kind.Reaction ||
43
43
  (opts.kind === Options_1.Kind.Transaction && outerOpts && (outerOpts.noSideEffects || outerOpts.kind === Options_1.Kind.Cache)) ||
44
44
  (opts.kind === Options_1.Kind.Cache && (oc.revision.snapshot.sealed ||
45
45
  oc.revision.prev.revision !== Snapshot_1.ROOT_REV));
@@ -72,7 +72,7 @@ class OperationController extends Controller_1.Controller {
72
72
  throw (0, Dbg_1.misuse)('a method is expected with reactronic decorator');
73
73
  op.options = new Hooks_1.OptionsImpl(op.options.getter, op.options.setter, op.options, options, false);
74
74
  if (Dbg_1.Dbg.isOn && Dbg_1.Dbg.trace.write)
75
- Dbg_1.Dbg.log('║', ' ✎', `${op.hint()}.options = ...`);
75
+ Dbg_1.Dbg.log('║', ' ✎', `${op.hint()}.options are changed`);
76
76
  return op.options;
77
77
  }
78
78
  static runWithin(op, func, ...args) {
@@ -107,7 +107,7 @@ class OperationController extends Controller_1.Controller {
107
107
  peek(args) {
108
108
  const ctx = Snapshot_1.Snapshot.current();
109
109
  const r = ctx.seekRevision(this.ownHolder, this.memberName);
110
- const op = this.peekFromRevision(r);
110
+ const op = this.peekFromRevision(r, args);
111
111
  const isValid = op.options.kind !== Options_1.Kind.Transaction && op.cause !== ROOT_TRIGGER &&
112
112
  (ctx === op.revision.snapshot || ctx.timestamp < op.obsoleteSince) &&
113
113
  (!op.options.sensitiveArgs || args === undefined ||
@@ -125,16 +125,17 @@ class OperationController extends Controller_1.Controller {
125
125
  const m = this.memberName;
126
126
  const ctx = Snapshot_1.Snapshot.edit();
127
127
  const r = ctx.getEditableRevision(h, m, Data_1.Meta.Holder, this);
128
- let op = this.peekFromRevision(r);
128
+ let op = this.peekFromRevision(r, undefined);
129
129
  if (op.revision !== r) {
130
130
  const op2 = new Operation(this, r, op);
131
- op = r.data[m] = op2.reenterOver(op);
131
+ r.data[m] = op2.reenterOver(op);
132
132
  ctx.bumpBy(r.prev.revision.snapshot.timestamp);
133
- Snapshot_1.Snapshot.markEdited(op, true, r, m, h);
133
+ Snapshot_1.Snapshot.markEdited(op, op2, true, r, m, h);
134
+ op = op2;
134
135
  }
135
136
  return { operation: op, isUpToDate: true, snapshot: ctx, revision: r };
136
137
  }
137
- peekFromRevision(r) {
138
+ peekFromRevision(r, args) {
138
139
  const m = this.memberName;
139
140
  let op = r.data[m];
140
141
  if (op.controller !== this) {
@@ -146,9 +147,13 @@ class OperationController extends Controller_1.Controller {
146
147
  let op2 = r2.data[m];
147
148
  if (op2.controller !== this) {
148
149
  r2 = Snapshot_1.Snapshot.edit().getEditableRevision(h, m, Data_1.Meta.Holder, this);
149
- op2 = r2.data[m] = new Operation(this, r2, op2);
150
- op2.cause = ROOT_TRIGGER;
151
- Snapshot_1.Snapshot.markEdited(op2, true, r2, m, h);
150
+ const t = new Operation(this, r2, op2);
151
+ if (args)
152
+ t.args = args;
153
+ t.cause = ROOT_TRIGGER;
154
+ r2.data[m] = t;
155
+ Snapshot_1.Snapshot.markEdited(op2, t, true, r2, m, h);
156
+ op2 = t;
152
157
  }
153
158
  return op2;
154
159
  });
@@ -162,7 +167,7 @@ class OperationController extends Controller_1.Controller {
162
167
  const result = Transaction_1.Transaction.runAs(opts, (argsx) => {
163
168
  if (!oc.operation.transaction.isCanceled) {
164
169
  oc = this.edit();
165
- if (Dbg_1.Dbg.isOn && (Dbg_1.Dbg.trace.transaction || Dbg_1.Dbg.trace.operation || Dbg_1.Dbg.trace.obsolete))
170
+ if (Dbg_1.Dbg.isOn && Dbg_1.Dbg.trace.operation)
166
171
  Dbg_1.Dbg.log('║', ' 𝑓', `${oc.operation.why()}`);
167
172
  oc.operation.run(this.ownHolder.proxy, argsx);
168
173
  }
@@ -170,7 +175,7 @@ class OperationController extends Controller_1.Controller {
170
175
  oc = this.peek(argsx);
171
176
  if (oc.operation.options.kind === Options_1.Kind.Transaction || !oc.isUpToDate) {
172
177
  oc = this.edit();
173
- if (Dbg_1.Dbg.isOn && (Dbg_1.Dbg.trace.transaction || Dbg_1.Dbg.trace.operation || Dbg_1.Dbg.trace.obsolete))
178
+ if (Dbg_1.Dbg.isOn && Dbg_1.Dbg.trace.operation)
174
179
  Dbg_1.Dbg.log('║', ' 𝑓', `${oc.operation.why()}`);
175
180
  oc.operation.run(this.ownHolder.proxy, argsx);
176
181
  }
@@ -211,8 +216,12 @@ class Operation extends Data_1.Observable {
211
216
  this.successor = undefined;
212
217
  }
213
218
  get isOperation() { return true; }
219
+ get selfSnapshotId() { return this.revision.snapshot.id; }
214
220
  hint() { return `${Snapshot_1.Dump.rev(this.revision, this.controller.memberName)}`; }
215
221
  get order() { return this.options.order; }
222
+ get ['#this']() {
223
+ return `Operation: ${this.why()}`;
224
+ }
216
225
  why() {
217
226
  let ms = Date.now();
218
227
  const prev = this.revision.prev.revision.data[this.controller.memberName];
@@ -224,7 +233,7 @@ class Operation extends Data_1.Observable {
224
233
  else if (this.controller.options.kind === Options_1.Kind.Transaction)
225
234
  trigger = ' << operation';
226
235
  else
227
- trigger = ` << called within ${this.revision.snapshot.hint}`;
236
+ trigger = ` << T${this.revision.snapshot.id}[${this.revision.snapshot.hint}]`;
228
237
  return `${this.hint()}${trigger} (${ms !== Infinity ? `${ms}ms since previous run` : 'initial run'})`;
229
238
  }
230
239
  briefWhy() {
@@ -369,18 +378,18 @@ class Operation extends Data_1.Observable {
369
378
  if (this.result instanceof Promise) {
370
379
  this.result = this.result.then(value => {
371
380
  this.value = value;
372
- this.leave(false, ' ', '- finished ', ' OK ──┘');
381
+ this.leave(false, ' ', '- finished ', ' OK ──┘');
373
382
  return value;
374
383
  }, error => {
375
384
  this.error = error;
376
- this.leave(false, ' ', '- finished ', 'ERR ──┘');
385
+ this.leave(false, ' ', '- finished ', 'ERR ──┘');
377
386
  throw error;
378
387
  });
379
388
  if (Dbg_1.Dbg.isOn) {
380
389
  if (Dbg_1.Dbg.trace.operation)
381
390
  Dbg_1.Dbg.log('║', '_/', `${this.hint()} - leave... `, 0, 'ASYNC ──┐');
382
391
  else if (Dbg_1.Dbg.trace.transaction)
383
- Dbg_1.Dbg.log('║', ' ', `${this.hint()}... `, 0, 'ASYNC');
392
+ Dbg_1.Dbg.log('║', ' ', `${this.why()} ...`, 0, 'ASYNC');
384
393
  }
385
394
  }
386
395
  else {
@@ -401,8 +410,8 @@ class Operation extends Data_1.Observable {
401
410
  monitorEnter(mon) {
402
411
  const options = {
403
412
  hint: 'Monitor.enter',
404
- standalone: true,
405
- trace: Dbg_1.Dbg.isOn && Dbg_1.Dbg.trace.monitor ? undefined : Dbg_1.Dbg.global,
413
+ standalone: 'isolated',
414
+ trace: Dbg_1.Dbg.isOn && Dbg_1.Dbg.trace.monitor ? undefined : Dbg_1.Dbg.global
406
415
  };
407
416
  OperationController.runWithin(undefined, Transaction_1.Transaction.runAs, options, Monitor_1.MonitorImpl.enter, mon, this.transaction);
408
417
  }
@@ -411,8 +420,8 @@ class Operation extends Data_1.Observable {
411
420
  const leave = () => {
412
421
  const options = {
413
422
  hint: 'Monitor.leave',
414
- standalone: true,
415
- trace: Dbg_1.Dbg.isOn && Dbg_1.Dbg.trace.monitor ? undefined : Dbg_1.Dbg.DefaultLevel,
423
+ standalone: 'isolated',
424
+ trace: Dbg_1.Dbg.isOn && Dbg_1.Dbg.trace.monitor ? undefined : Dbg_1.Dbg.DefaultLevel
416
425
  };
417
426
  OperationController.runWithin(undefined, Transaction_1.Transaction.runAs, options, Monitor_1.MonitorImpl.leave, mon, this.transaction);
418
427
  };
@@ -443,10 +452,10 @@ class Operation extends Data_1.Observable {
443
452
  }
444
453
  }
445
454
  }
446
- static markEdited(value, edited, r, m, h) {
455
+ static markEdited(oldValue, newValue, edited, r, m, h) {
447
456
  edited ? r.changes.set(m, Operation.current) : r.changes.delete(m);
448
457
  if (Dbg_1.Dbg.isOn && Dbg_1.Dbg.trace.write)
449
- 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)');
458
+ 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)');
450
459
  }
451
460
  static isConflicting(oldValue, newValue) {
452
461
  let result = oldValue !== newValue;
@@ -518,29 +527,33 @@ class Operation extends Data_1.Observable {
518
527
  this.observables = undefined;
519
528
  }
520
529
  subscribeTo(observable, r, m, h, timestamp) {
521
- var _a, _b;
522
- const isValid = Operation.isValid(observable, r, m, h, timestamp);
523
- if (isValid) {
530
+ var _a, _b, _c;
531
+ const ok = Operation.canSubscribe(observable, r, m, h, timestamp);
532
+ if (ok) {
524
533
  let times = 0;
525
534
  if (Hooks_1.Hooks.repetitiveUsageWarningThreshold < Number.MAX_SAFE_INTEGER) {
526
535
  const existing = this.observables.get(observable);
527
536
  times = existing ? existing.usageCount + 1 : 1;
528
537
  }
529
- if (!observable.observers)
530
- observable.observers = new Set();
531
- const info = { revision: r, memberName: m, usageCount: times };
532
- observable.observers.add(this);
533
- this.observables.set(observable, info);
534
- if (Dbg_1.Dbg.isOn && (Dbg_1.Dbg.trace.read || ((_a = this.options.trace) === null || _a === void 0 ? void 0 : _a.read)))
535
- Dbg_1.Dbg.log('║', ' ∞ ', `${this.hint()} is subscribed to ${Snapshot_1.Dump.rev(r, m)}${info.usageCount > 1 ? ` (${info.usageCount} times)` : ''}`);
538
+ if (this.observables !== undefined) {
539
+ if (!observable.observers)
540
+ observable.observers = new Set();
541
+ const info = { revision: r, memberName: m, usageCount: times };
542
+ observable.observers.add(this);
543
+ this.observables.set(observable, info);
544
+ if (Dbg_1.Dbg.isOn && (Dbg_1.Dbg.trace.read || ((_a = this.options.trace) === null || _a === void 0 ? void 0 : _a.read)))
545
+ Dbg_1.Dbg.log('║', ' ∞ ', `${this.hint()} is subscribed to ${Snapshot_1.Dump.rev(r, m)}${info.usageCount > 1 ? ` (${info.usageCount} times)` : ''}`);
546
+ }
547
+ else if (Dbg_1.Dbg.isOn && (Dbg_1.Dbg.trace.read || ((_b = this.options.trace) === null || _b === void 0 ? void 0 : _b.read)))
548
+ Dbg_1.Dbg.log('║', ' x ', `${this.hint()} is obsolete and is NOT subscribed to ${Snapshot_1.Dump.rev(r, m)}`);
536
549
  }
537
550
  else {
538
- if (Dbg_1.Dbg.isOn && (Dbg_1.Dbg.trace.read || ((_b = this.options.trace) === null || _b === void 0 ? void 0 : _b.read)))
551
+ if (Dbg_1.Dbg.isOn && (Dbg_1.Dbg.trace.read || ((_c = this.options.trace) === null || _c === void 0 ? void 0 : _c.read)))
539
552
  Dbg_1.Dbg.log('║', ' x ', `${this.hint()} is NOT subscribed to already obsolete ${Snapshot_1.Dump.rev(r, m)}`);
540
553
  }
541
- return isValid;
554
+ return ok;
542
555
  }
543
- static isValid(observable, r, m, h, timestamp) {
556
+ static canSubscribe(observable, r, m, h, timestamp) {
544
557
  let result = !r.snapshot.sealed || observable === h.head.data[m];
545
558
  if (result && timestamp !== -1)
546
559
  result = !(observable instanceof Operation && timestamp >= observable.obsoleteSince);
@@ -618,7 +631,7 @@ function propagationHint(cause, full) {
618
631
  full && result.push(cause.revision.snapshot.hint);
619
632
  return result;
620
633
  }
621
- function valueHint(value) {
634
+ function valueHint(value, m) {
622
635
  let result = '';
623
636
  if (Array.isArray(value))
624
637
  result = `Array(${value.length})`;
@@ -627,13 +640,13 @@ function valueHint(value) {
627
640
  else if (value instanceof Map)
628
641
  result = `Map(${value.size})`;
629
642
  else if (value instanceof Operation)
630
- result = `<rerun over ${Snapshot_1.Dump.rev(value.revision.prev.revision)}>`;
643
+ result = `${Snapshot_1.Dump.rev(value.revision, m)}`;
631
644
  else if (value === Data_1.Meta.Disposed)
632
645
  result = '<disposed>';
633
646
  else if (value !== undefined && value !== null)
634
647
  result = value.toString().slice(0, 20);
635
648
  else
636
- result = '';
649
+ result = '';
637
650
  return result;
638
651
  }
639
652
  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,7 +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>;
27
+ static isFrameOver(everyN?: number, timeLimit?: number): boolean;
28
+ static requestNextFrame(sleepTime?: number): Promise<void>;
29
29
  static get isCanceled(): boolean;
30
30
  }
@@ -22,8 +22,8 @@ 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(); }
25
+ static isFrameOver(everyN = 1, timeLimit = 14) { return TransactionImpl.isFrameOver(everyN, timeLimit); }
26
+ static requestNextFrame(sleepTime = 0) { return TransactionImpl.requestNextFrame(sleepTime); }
27
27
  static get isCanceled() { return TransactionImpl.current.isCanceled; }
28
28
  }
29
29
  exports.Transaction = Transaction;
@@ -124,10 +124,11 @@ class TransactionImpl extends Transaction {
124
124
  t.guard();
125
125
  let result = t.runImpl(options === null || options === void 0 ? void 0 : options.trace, func, ...args);
126
126
  if (root) {
127
- if (result instanceof Promise)
127
+ if (result instanceof Promise) {
128
128
  result = TransactionImpl.standalone(() => {
129
129
  return t.wrapToRetry(t.wrapToWaitUntilFinish(result), func, ...args);
130
130
  });
131
+ }
131
132
  t.seal();
132
133
  }
133
134
  return result;
@@ -142,22 +143,24 @@ class TransactionImpl extends Transaction {
142
143
  TransactionImpl.curr = outer;
143
144
  }
144
145
  }
145
- static isTimeOver(everyN = 1) {
146
- TransactionImpl.checkCount++;
147
- let result = TransactionImpl.checkCount % everyN === 0;
146
+ static isFrameOver(everyN = 1, timeLimit = 14) {
147
+ TransactionImpl.frameOverCounter++;
148
+ let result = TransactionImpl.frameOverCounter % everyN === 0;
148
149
  if (result) {
149
- const ms = performance.now() - TransactionImpl.startTime;
150
- result = ms > TransactionImpl.timeLimit;
150
+ const ms = performance.now() - TransactionImpl.frameStartTime;
151
+ result = ms > timeLimit;
151
152
  }
152
153
  return result;
153
154
  }
154
- static requestMoreTime(sleepTime = 0) {
155
+ static requestNextFrame(sleepTime = 0) {
155
156
  return (0, Utils_1.pause)(sleepTime);
156
157
  }
157
158
  static acquire(options) {
158
- return (options === null || options === void 0 ? void 0 : options.standalone) || TransactionImpl.curr.isFinished
159
- ? new TransactionImpl(options)
160
- : TransactionImpl.curr;
159
+ const curr = TransactionImpl.curr;
160
+ if ((options === null || options === void 0 ? void 0 : options.standalone) || curr.isFinished || curr.options.standalone === 'isolated')
161
+ return new TransactionImpl(options);
162
+ else
163
+ return TransactionImpl.curr;
161
164
  }
162
165
  guard() {
163
166
  if (this.sealed && TransactionImpl.curr !== this)
@@ -177,7 +180,7 @@ class TransactionImpl extends Transaction {
177
180
  yield this.after.whenFinished();
178
181
  const options = {
179
182
  hint: `${this.hint} - restart after T${this.after.id}`,
180
- standalone: true,
183
+ standalone: this.options.standalone === 'isolated' ? 'isolated' : true,
181
184
  trace: this.snapshot.options.trace,
182
185
  token: this.snapshot.options.token,
183
186
  };
@@ -203,8 +206,8 @@ class TransactionImpl extends Transaction {
203
206
  const outer = TransactionImpl.curr;
204
207
  try {
205
208
  if (outer === TransactionImpl.none) {
206
- TransactionImpl.startTime = performance.now();
207
- TransactionImpl.checkCount = 0;
209
+ TransactionImpl.frameStartTime = performance.now();
210
+ TransactionImpl.frameOverCounter = 0;
208
211
  }
209
212
  TransactionImpl.curr = this;
210
213
  this.pending++;
@@ -261,7 +264,7 @@ class TransactionImpl extends Transaction {
261
264
  applyOrDiscard() {
262
265
  try {
263
266
  if (Dbg_1.Dbg.isOn && Dbg_1.Dbg.trace.change)
264
- Dbg_1.Dbg.log('╠══', '', '', undefined, ' changes');
267
+ Dbg_1.Dbg.log('╠═', '', '', undefined, 'changes');
265
268
  this.snapshot.applyOrDiscard(this.canceled);
266
269
  this.snapshot.collectGarbage();
267
270
  if (this.promise) {
@@ -306,7 +309,6 @@ class TransactionImpl extends Transaction {
306
309
  TransactionImpl.none = new TransactionImpl({ hint: '<none>' });
307
310
  TransactionImpl.curr = TransactionImpl.none;
308
311
  TransactionImpl.inspection = false;
309
- TransactionImpl.startTime = 0;
310
- TransactionImpl.timeLimit = 14;
311
- TransactionImpl.checkCount = 0;
312
+ TransactionImpl.frameStartTime = 0;
313
+ TransactionImpl.frameOverCounter = 0;
312
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
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "reactronic",
3
- "version": "0.21.523",
3
+ "version": "0.21.527",
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",