reactronic 0.24.304 → 0.24.306

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.
@@ -18,6 +18,7 @@ export type MemberOptions = {
18
18
  readonly triggeringArgs: boolean;
19
19
  readonly throttling: number;
20
20
  readonly reentrance: Reentrance;
21
+ readonly allowObsoleteToFinish: boolean;
21
22
  readonly journal: Journal | undefined;
22
23
  readonly indicator: Indicator | null;
23
24
  readonly logging?: Partial<LoggingOptions>;
@@ -49,7 +50,7 @@ export type Operation<T> = {
49
50
  readonly result: T;
50
51
  readonly error: any;
51
52
  readonly stamp: number;
52
- readonly isUpToDate: boolean;
53
+ readonly isReusable: boolean;
53
54
  configure(options: Partial<MemberOptions>): MemberOptions;
54
55
  markObsolete(): void;
55
56
  pullLastResult(args?: any[]): T | undefined;
@@ -26,7 +26,10 @@ export declare class Changeset implements AbstractChangeset {
26
26
  static edit: () => Changeset;
27
27
  static markUsed: (fv: FieldVersion, ov: ObjectVersion, fk: FieldKey, h: ObjectHandle, kind: Kind, weak: boolean) => void;
28
28
  static markEdited: (oldValue: any, newValue: any, edited: boolean, ov: ObjectVersion, fk: FieldKey, h: ObjectHandle) => void;
29
- static isConflicting: (oldValue: any, newValue: any) => boolean;
29
+ static tryResolveConflict: (theirValue: any, ourFormerValue: any, ourValue: any) => {
30
+ isResolved: boolean;
31
+ resolvedValue: any;
32
+ };
30
33
  static propagateAllChangesThroughSubscriptions: (changeset: Changeset) => void;
31
34
  static revokeAllSubscriptions: (changeset: Changeset) => void;
32
35
  static enqueueReactiveFunctionsToRun: (reactive: Array<Observer>) => void;
@@ -157,7 +157,7 @@ export class Changeset {
157
157
  let conflicts = undefined;
158
158
  if (this.items.size > 0) {
159
159
  this.items.forEach((ov, h) => {
160
- const theirs = this.parent ? this.parent.lookupObjectVersion(h, Meta.Handle, false) : h.applied;
160
+ const theirs = this.parent ? this.parent.lookupObjectVersion(h, Meta.Handle, true) : h.applied;
161
161
  if (ov.former.objectVersion !== theirs) {
162
162
  const merged = this.merge(h, ov, theirs);
163
163
  if (ov.conflicts.size > 0) {
@@ -202,11 +202,15 @@ export class Changeset {
202
202
  }
203
203
  }
204
204
  else {
205
- const conflict = Changeset.isConflicting(theirs.data[fk], ours.former.objectVersion.data[fk]);
206
- if (conflict)
205
+ const theirValue = theirs.data[fk];
206
+ const ourFormerValue = ours.former.objectVersion.data[fk];
207
+ const { isResolved, resolvedValue } = Changeset.tryResolveConflict(theirValue, ourFormerValue, ourFieldVersion);
208
+ if (!isResolved)
207
209
  ours.conflicts.set(fk, theirs);
210
+ else if (resolvedValue.isLaunch)
211
+ merged[fk] = resolvedValue;
208
212
  if (Log.isOn && Log.opt.change)
209
- Log.write("║╠", "", `${Dump.snapshot2(h, ours.changeset, fk)} ${conflict ? "<>" : "=="} ${Dump.snapshot2(h, theirs.changeset, fk)}`, 0, conflict ? " *** CONFLICT ***" : undefined);
213
+ Log.write("║╠", "", `${Dump.snapshot2(h, ours.changeset, fk)} ${!isResolved ? "<>" : "=="} ${Dump.snapshot2(h, theirs.changeset, fk)}`, 0, !isResolved ? " *** CONFLICT ***" : undefined);
210
214
  }
211
215
  });
212
216
  Utils.copyAllMembers(merged, ours.data);
@@ -311,7 +315,7 @@ Changeset.current = UNDEF;
311
315
  Changeset.edit = UNDEF;
312
316
  Changeset.markUsed = UNDEF;
313
317
  Changeset.markEdited = UNDEF;
314
- Changeset.isConflicting = UNDEF;
318
+ Changeset.tryResolveConflict = UNDEF;
315
319
  Changeset.propagateAllChangesThroughSubscriptions = (changeset) => { };
316
320
  Changeset.revokeAllSubscriptions = (changeset) => { };
317
321
  Changeset.enqueueReactiveFunctionsToRun = (reactive) => { };
@@ -131,13 +131,13 @@ export class IndicatorImpl extends Indicator {
131
131
  }
132
132
  static tick(mon) {
133
133
  if (mon.internals.started !== 0) {
134
- Transaction.run(INDICATOR_TICK_OPTIONS, () => {
135
- const resolution = mon.internals.durationResolution;
136
- mon.busyDuration = Math.round(resolution * (performance.now() - mon.internals.started)) / resolution;
137
- });
134
+ const resolution = mon.internals.durationResolution;
135
+ mon.busyDuration = Math.round(resolution * (performance.now() - mon.internals.started)) / resolution;
138
136
  const t = globalThis !== null && globalThis !== void 0 ? globalThis : global;
139
137
  if (t.requestAnimationFrame)
140
- requestAnimationFrame(() => IndicatorImpl.tick(mon));
138
+ requestAnimationFrame(() => {
139
+ Transaction.run(INDICATOR_TICK_OPTIONS, () => IndicatorImpl.tick(mon));
140
+ });
141
141
  }
142
142
  }
143
143
  }
@@ -24,6 +24,7 @@ export declare class OptionsImpl implements MemberOptions {
24
24
  readonly triggeringArgs: boolean;
25
25
  readonly throttling: number;
26
26
  readonly reentrance: Reentrance;
27
+ readonly allowObsoleteToFinish: boolean;
27
28
  readonly journal: Journal | undefined;
28
29
  readonly indicator: Indicator | null;
29
30
  readonly logging?: Partial<LoggingOptions>;
@@ -33,6 +33,7 @@ const DEFAULT_OPTIONS = Object.freeze({
33
33
  triggeringArgs: false,
34
34
  throttling: Number.MAX_SAFE_INTEGER,
35
35
  reentrance: Reentrance.preventWithError,
36
+ allowObsoleteToFinish: false,
36
37
  journal: undefined,
37
38
  indicator: null,
38
39
  logging: undefined,
@@ -48,6 +49,7 @@ export class OptionsImpl {
48
49
  this.triggeringArgs = merge(DEFAULT_OPTIONS.triggeringArgs, existing.triggeringArgs, patch.triggeringArgs, implicit);
49
50
  this.throttling = merge(DEFAULT_OPTIONS.throttling, existing.throttling, patch.throttling, implicit);
50
51
  this.reentrance = merge(DEFAULT_OPTIONS.reentrance, existing.reentrance, patch.reentrance, implicit);
52
+ this.allowObsoleteToFinish = merge(DEFAULT_OPTIONS.allowObsoleteToFinish, existing.allowObsoleteToFinish, patch.allowObsoleteToFinish, implicit);
51
53
  this.journal = merge(DEFAULT_OPTIONS.journal, existing.journal, patch.journal, implicit);
52
54
  this.indicator = merge(DEFAULT_OPTIONS.indicator, existing.indicator, patch.indicator, implicit);
53
55
  this.logging = merge(DEFAULT_OPTIONS.logging, existing.logging, patch.logging, implicit);
@@ -13,7 +13,7 @@ export declare class OperationImpl implements Operation<any> {
13
13
  get result(): any;
14
14
  get error(): boolean;
15
15
  get stamp(): number;
16
- get isUpToDate(): boolean;
16
+ get isReusable(): boolean;
17
17
  markObsolete(): void;
18
18
  pullLastResult(args?: any[]): any;
19
19
  constructor(h: ObjectHandle, fk: FieldKey);
@@ -74,7 +74,7 @@ declare class Launch extends FieldVersion implements Observer {
74
74
  private static processDeferredReactiveFunctions;
75
75
  private static markUsed;
76
76
  private static markEdited;
77
- private static isConflicting;
77
+ private static tryResolveConflict;
78
78
  private static propagateAllChangesThroughSubscriptions;
79
79
  private static revokeAllSubscriptions;
80
80
  private static propagateFieldChangeThroughSubscriptions;
@@ -17,7 +17,7 @@ export class OperationImpl {
17
17
  get result() { return this.reuseOrRelaunch(true, undefined).content; }
18
18
  get error() { return this.use().launch.error; }
19
19
  get stamp() { return this.use().objectVersion.changeset.timestamp; }
20
- get isUpToDate() { return this.use().isUpToDate; }
20
+ get isReusable() { return this.use().isReusable; }
21
21
  markObsolete() { Transaction.run({ hint: Log.isOn ? `markObsolete(${Dump.obj(this.ownerHandle, this.fieldKey)})` : "markObsolete()" }, OperationImpl.markObsolete, this); }
22
22
  pullLastResult(args) { return this.reuseOrRelaunch(true, args).content; }
23
23
  constructor(h, fk) {
@@ -29,7 +29,7 @@ export class OperationImpl {
29
29
  const ctx = ror.changeset;
30
30
  const launch = ror.launch;
31
31
  const opts = launch.options;
32
- if (!ror.isUpToDate && !ror.objectVersion.disposed
32
+ if (!ror.isReusable && !ror.objectVersion.disposed
33
33
  && (!weak || launch.cause === BOOT_CAUSE || !launch.successor ||
34
34
  launch.successor.transaction.isFinished)) {
35
35
  const isolation = !weak ? opts.isolation : Isolation.disjoinFromOuterTransaction;
@@ -98,11 +98,12 @@ export class OperationImpl {
98
98
  const ctx = Changeset.current();
99
99
  const ov = ctx.lookupObjectVersion(this.ownerHandle, this.fieldKey, false);
100
100
  const launch = this.acquireFromObjectVersion(ov, args);
101
- const isValid = launch.options.kind !== Kind.transactional && launch.cause !== BOOT_CAUSE &&
102
- (ctx === launch.changeset || ctx.timestamp < launch.obsoleteSince) &&
101
+ const applied = this.ownerHandle.applied.data[this.fieldKey];
102
+ const isReusable = launch.options.kind !== Kind.transactional && launch.cause !== BOOT_CAUSE &&
103
+ (ctx === launch.changeset || ctx.timestamp < launch.obsoleteSince || applied.obsoleteDueTo === undefined) &&
103
104
  (!launch.options.triggeringArgs || args === undefined ||
104
105
  launch.args.length === args.length && launch.args.every((t, i) => t === args[i])) || ov.disposed;
105
- return { launch, isUpToDate: isValid, changeset: ctx, objectVersion: ov };
106
+ return { launch, isReusable, changeset: ctx, objectVersion: ov };
106
107
  }
107
108
  use() {
108
109
  const ror = this.peek(undefined);
@@ -122,7 +123,7 @@ export class OperationImpl {
122
123
  Changeset.markEdited(launch, relaunch, true, ov, fk, h);
123
124
  launch = relaunch;
124
125
  }
125
- return { launch, isUpToDate: true, changeset: ctx, objectVersion: ov };
126
+ return { launch, isReusable: true, changeset: ctx, objectVersion: ov };
126
127
  }
127
128
  acquireFromObjectVersion(ov, args) {
128
129
  const fk = this.fieldKey;
@@ -174,7 +175,7 @@ export class OperationImpl {
174
175
  }
175
176
  else {
176
177
  ror = this.peek(argsx);
177
- if (ror.launch.options.kind === Kind.transactional || !ror.isUpToDate) {
178
+ if (ror.launch.options.kind === Kind.transactional || !ror.isReusable) {
178
179
  ror = this.edit();
179
180
  if (Log.isOn && Log.opt.operation)
180
181
  Log.write("║", " o", `${ror.launch.why()}`);
@@ -189,7 +190,8 @@ export class OperationImpl {
189
190
  static markObsolete(self) {
190
191
  const ror = self.peek(undefined);
191
192
  const ctx = ror.changeset;
192
- ror.launch.markObsoleteDueTo(ror.launch, self.fieldKey, EMPTY_OBJECT_VERSION.changeset, EMPTY_HANDLE, BOOT_CAUSE, ctx.timestamp, ctx.obsolete);
193
+ const obsolete = ror.launch.transaction.isFinished ? ctx.obsolete : ror.launch.transaction.changeset.obsolete;
194
+ ror.launch.markObsoleteDueTo(ror.launch, self.fieldKey, EMPTY_OBJECT_VERSION.changeset, EMPTY_HANDLE, BOOT_CAUSE, ctx.timestamp, obsolete);
193
195
  }
194
196
  }
195
197
  class Launch extends FieldVersion {
@@ -307,7 +309,7 @@ class Launch extends FieldVersion {
307
309
  const tran = this.transaction;
308
310
  if (tran.changeset === changeset) {
309
311
  }
310
- else if (!tran.isFinished && this !== observable)
312
+ else if (!tran.isFinished && this !== observable && !this.options.allowObsoleteToFinish)
311
313
  tran.cancel(new Error(`T${tran.id}[${tran.hint}] is canceled due to obsolete ${Dump.snapshot2(h, changeset, fk)} changed by T${changeset.id}[${changeset.hint}]`), null);
312
314
  }
313
315
  else if (Log.isOn && (Log.opt.obsolete || ((_c = this.options.logging) === null || _c === void 0 ? void 0 : _c.obsolete)))
@@ -485,11 +487,20 @@ class Launch extends FieldVersion {
485
487
  if (Log.isOn && Log.opt.write)
486
488
  edited ? Log.write("║", " =", `${Dump.snapshot2(h, ov.changeset, fk)} is changed: ${valueHint(oldValue)} ▸▸ ${valueHint(newValue)}`) : Log.write("║", " =", `${Dump.snapshot2(h, ov.changeset, fk)} is changed: ${valueHint(oldValue)} ▸▸ ${valueHint(newValue)}`, undefined, " (same as previous)");
487
489
  }
488
- static isConflicting(oldValue, newValue) {
489
- let result = oldValue !== newValue;
490
- if (result)
491
- result = oldValue instanceof Launch && oldValue.cause !== BOOT_CAUSE;
492
- return result;
490
+ static tryResolveConflict(theirValue, ourFormerValue, ourValue) {
491
+ let isResolved = theirValue === ourFormerValue;
492
+ let resolvedValue = ourValue;
493
+ if (!isResolved) {
494
+ if (ourValue instanceof Launch && ourValue.obsoleteDueTo === undefined) {
495
+ isResolved = true;
496
+ resolvedValue = ourValue;
497
+ }
498
+ else if (theirValue instanceof Launch && (theirValue.obsoleteDueTo === undefined || theirValue.cause === BOOT_CAUSE)) {
499
+ isResolved = true;
500
+ resolvedValue = theirValue;
501
+ }
502
+ }
503
+ return { isResolved, resolvedValue };
493
504
  }
494
505
  static propagateAllChangesThroughSubscriptions(changeset) {
495
506
  var _a;
@@ -533,7 +544,11 @@ class Launch extends FieldVersion {
533
544
  else
534
545
  former.successor = undefined;
535
546
  }
536
- (_a = former.observers) === null || _a === void 0 ? void 0 : _a.forEach(s => s.markObsoleteDueTo(former, fk, ov.changeset, h, why, timestamp, obsolete));
547
+ (_a = former.observers) === null || _a === void 0 ? void 0 : _a.forEach(s => {
548
+ const t = s.transaction;
549
+ const o = t.isFinished ? obsolete : t.changeset.obsolete;
550
+ return s.markObsoleteDueTo(former, fk, ov.changeset, h, why, timestamp, o);
551
+ });
537
552
  }
538
553
  }
539
554
  if (curr instanceof Launch) {
@@ -653,7 +668,7 @@ class Launch extends FieldVersion {
653
668
  Dump.valueHint = valueHint;
654
669
  Changeset.markUsed = Launch.markUsed;
655
670
  Changeset.markEdited = Launch.markEdited;
656
- Changeset.isConflicting = Launch.isConflicting;
671
+ Changeset.tryResolveConflict = Launch.tryResolveConflict;
657
672
  Changeset.propagateAllChangesThroughSubscriptions = Launch.propagateAllChangesThroughSubscriptions;
658
673
  Changeset.revokeAllSubscriptions = Launch.revokeAllSubscriptions;
659
674
  Changeset.enqueueReactiveFunctionsToRun = Launch.enqueueReactiveFunctionsToRun;
@@ -354,7 +354,8 @@ RxNodeImpl.disposableNodeCount = 0;
354
354
  __decorate([
355
355
  reactive,
356
356
  options({
357
- reentrance: Reentrance.cancelPrevious,
357
+ reentrance: Reentrance.cancelAndWaitPrevious,
358
+ allowObsoleteToFinish: true,
358
359
  triggeringArgs: true,
359
360
  noSideEffects: false,
360
361
  }),
@@ -12,6 +12,7 @@ export declare abstract class Transaction implements Worker {
12
12
  abstract readonly error: Error | undefined;
13
13
  abstract readonly changeset: Changeset;
14
14
  abstract readonly margin: number;
15
+ abstract readonly parent?: Transaction;
15
16
  abstract run<T>(func: F<T>, ...args: any[]): T;
16
17
  abstract inspect<T>(func: F<T>, ...args: any[]): T;
17
18
  abstract apply(): void;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "reactronic",
3
- "version": "0.24.304",
3
+ "version": "0.24.306",
4
4
  "description": "Reactronic - Transactional Reactive State Management",
5
5
  "publisher": "Nezaboodka Software",
6
6
  "license": "Apache-2.0",