reactronic 0.22.311 → 0.22.314

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
@@ -4,7 +4,7 @@
4
4
  [![NPM Version](https://img.shields.io/npm/v/reactronic.svg?style=flat&colorB=success)](https://www.npmjs.com/package/reactronic)
5
5
  [![Package Size](https://img.shields.io/bundlephobia/minzip/reactronic.svg?colorB=success)](https://bundlephobia.com/result?p=reactronic)
6
6
  ![Coverage](https://img.shields.io/badge/coverage-97%25-success.svg)
7
- ![Lines](https://img.shields.io/badge/lines-1811-success.svg)
7
+ ![Lines](https://img.shields.io/badge/lines-2722-success.svg)
8
8
  [![Demo](https://img.shields.io/badge/demo-live-success.svg)](https://gitlab.com/nezaboodka/nevod.website/-/blob/master/README.md)
9
9
 
10
10
  # **Reactronic** - Transactional Reactive State Management
@@ -1,4 +1,5 @@
1
1
  export { all, pause } from './util/Utils';
2
+ export { Collection } from './util/Collection';
2
3
  export { SealedArray } from './util/SealedArray';
3
4
  export { SealedMap } from './util/SealedMap';
4
5
  export { SealedSet } from './util/SealedSet';
@@ -6,7 +7,9 @@ export { MemberOptions, SnapshotOptions, Kind, Reentrance, LoggingOptions, Profi
6
7
  export { Worker } from './Worker';
7
8
  export { Controller } from './Controller';
8
9
  export { Ref, ToggleRef, BoolOnly, GivenTypeOnly } from './Ref';
9
- export { ReactiveObject, ReactiveArray, ReactiveMap } from './impl/Hooks';
10
+ export { ReactiveObject } from './impl/Hooks';
11
+ export { ReactiveArray } from './impl/ReactiveArray';
12
+ export { ReactiveMap } from './impl/ReactiveMap';
10
13
  export { Changeset } from './impl/Changeset';
11
14
  export { Transaction } from './impl/Transaction';
12
15
  export { Monitor } from './impl/Monitor';
@@ -1,11 +1,14 @@
1
1
  export { all, pause } from './util/Utils';
2
+ export { Collection } from './util/Collection';
2
3
  export { SealedArray } from './util/SealedArray';
3
4
  export { SealedMap } from './util/SealedMap';
4
5
  export { SealedSet } from './util/SealedSet';
5
6
  export { Kind, Reentrance, LoggingLevel } from './Options';
6
7
  export { Controller } from './Controller';
7
8
  export { Ref, ToggleRef } from './Ref';
8
- export { ReactiveObject, ReactiveArray, ReactiveMap } from './impl/Hooks';
9
+ export { ReactiveObject } from './impl/Hooks';
10
+ export { ReactiveArray } from './impl/ReactiveArray';
11
+ export { ReactiveMap } from './impl/ReactiveMap';
9
12
  export { Changeset } from './impl/Changeset';
10
13
  export { Transaction } from './impl/Transaction';
11
14
  export { Monitor } from './impl/Monitor';
@@ -29,9 +29,9 @@ export declare class Changeset implements AbstractChangeset {
29
29
  static propagateAllChangesThroughSubscriptions: (changeset: Changeset) => void;
30
30
  static revokeAllSubscriptions: (changeset: Changeset) => void;
31
31
  static enqueueReactionsToRun: (reactions: Array<Subscriber>) => void;
32
- seekSnapshot(h: ObjectHandle, m: MemberName): ObjectSnapshot;
33
- getRelevantSnapshot(h: ObjectHandle, m: MemberName): ObjectSnapshot;
34
- getEditableSnapshot(h: ObjectHandle, m: MemberName, value: any, token?: any): ObjectSnapshot;
32
+ lookupObjectSnapshot(h: ObjectHandle, m: MemberName): ObjectSnapshot;
33
+ getObjectSnapshot(h: ObjectHandle, m: MemberName): ObjectSnapshot;
34
+ getEditableObjectSnapshot(h: ObjectHandle, m: MemberName, value: any, token?: any): ObjectSnapshot;
35
35
  static takeSnapshot<T>(obj: T): T;
36
36
  static dispose(obj: any): void;
37
37
  static doDispose(ctx: Changeset, h: ObjectHandle): ObjectSnapshot;
@@ -11,7 +11,7 @@ Object.defineProperty(ObjectHandle.prototype, '#this', {
11
11
  configurable: false, enumerable: false,
12
12
  get() {
13
13
  const result = {};
14
- const data = Changeset.current().getRelevantSnapshot(this, '#this').data;
14
+ const data = Changeset.current().getObjectSnapshot(this, '#this').data;
15
15
  for (const m in data) {
16
16
  const v = data[m];
17
17
  if (v instanceof Subscription)
@@ -38,7 +38,7 @@ export class Changeset {
38
38
  }
39
39
  get hint() { var _a; return (_a = this.options.hint) !== null && _a !== void 0 ? _a : 'noname'; }
40
40
  get timestamp() { return this.revision; }
41
- seekSnapshot(h, m) {
41
+ lookupObjectSnapshot(h, m) {
42
42
  let os = h.editing;
43
43
  if (os && os.changeset !== this) {
44
44
  os = this.items.get(h);
@@ -52,14 +52,14 @@ export class Changeset {
52
52
  }
53
53
  return os;
54
54
  }
55
- getRelevantSnapshot(h, m) {
56
- const r = this.seekSnapshot(h, m);
55
+ getObjectSnapshot(h, m) {
56
+ const r = this.lookupObjectSnapshot(h, m);
57
57
  if (r === EMPTY_SNAPSHOT)
58
- throw misuse(`object ${Dump.obj(h)} doesn't exist in snapshot v${this.revision} (${this.hint})`);
58
+ throw misuse(`cannot use data from a transaction started after the current one T${this.id}[${this.hint}]: ${Dump.obj(h, m)} (head is T${h.head.changeset.id}[${h.head.changeset.hint}]${h.editing ? `, uncommitted T${h.editing.changeset.id}[${h.editing.changeset.hint}]` : ''})`);
59
59
  return r;
60
60
  }
61
- getEditableSnapshot(h, m, value, token) {
62
- let os = this.seekSnapshot(h, m);
61
+ getEditableObjectSnapshot(h, m, value, token) {
62
+ let os = this.lookupObjectSnapshot(h, m);
63
63
  const existing = os.data[m];
64
64
  if (existing !== Meta.Nonreactive) {
65
65
  if (this.isNewSnapshotRequired(h, os, m, existing, value, token)) {
@@ -90,7 +90,7 @@ export class Changeset {
90
90
  Changeset.doDispose(ctx, h);
91
91
  }
92
92
  static doDispose(ctx, h) {
93
- const os = ctx.getEditableSnapshot(h, Meta.Revision, Meta.Undefined);
93
+ const os = ctx.getEditableObjectSnapshot(h, Meta.Revision, Meta.Undefined);
94
94
  if (os !== EMPTY_SNAPSHOT)
95
95
  os.disposed = true;
96
96
  return os;
@@ -98,13 +98,15 @@ export class Changeset {
98
98
  isNewSnapshotRequired(h, os, m, existing, value, token) {
99
99
  if (this.sealed && os.changeset !== EMPTY_SNAPSHOT.changeset)
100
100
  throw misuse(`reactive property ${Dump.obj(h, m)} can only be modified inside transaction`);
101
- if (m !== Meta.Handle && value !== Meta.Handle) {
102
- if (os.changeset !== this || os.former.snapshot !== EMPTY_SNAPSHOT) {
103
- if (this.options.token !== undefined && token !== this.options.token)
104
- throw misuse(`${this.hint} should not have side effects (trying to change ${Dump.snapshot(os, m)})`);
101
+ if (m !== Meta.Handle) {
102
+ if (value !== Meta.Handle) {
103
+ if (os.changeset !== this || os.former.snapshot !== EMPTY_SNAPSHOT) {
104
+ if (this.options.token !== undefined && token !== this.options.token)
105
+ throw misuse(`${this.hint} should not have side effects (trying to change ${Dump.snapshot(os, m)})`);
106
+ }
105
107
  }
106
108
  if (os === EMPTY_SNAPSHOT)
107
- throw misuse(`member ${Dump.snapshot(os, m)} doesn't exist in snapshot v${this.revision} (${this.hint})`);
109
+ throw misuse(`cannot use data from a transaction started after the current one T${this.id}[${this.hint}]: ${Dump.snapshot(os, m)} (head is T${h.head.changeset.id}[${h.head.changeset.hint}]${h.editing ? `, uncommitted T${h.editing.changeset.id}[${h.editing.changeset.hint}]` : ''})`);
108
110
  }
109
111
  return os.changeset !== this && !this.sealed;
110
112
  }
@@ -4,55 +4,12 @@ import { LoggingOptions, ProfilingOptions } from '../Logging';
4
4
  import { MemberName, ObjectHandle, StandaloneMode } from './Data';
5
5
  import { Journal } from './Journal';
6
6
  import { Monitor } from './Monitor';
7
- export declare abstract class ReactiveObject {
8
- constructor();
7
+ export declare abstract class HookedObject {
8
+ protected constructor(reactive: boolean);
9
9
  [Symbol.toStringTag](): string;
10
10
  }
11
- export declare class ReactiveArray<T> extends ReactiveObject {
12
- private a;
13
- get length(): number;
14
- set length(n: number);
15
- get(n: number): T;
16
- set(n: number, item: T): void;
17
- toString(): string;
18
- toLocaleString(): string;
19
- pop(): T | undefined;
20
- push(...items: T[]): number;
21
- concat(...items: (T | ConcatArray<T>)[]): T[];
22
- join(separator?: string): string;
23
- reverse(): T[];
24
- shift(): T | undefined;
25
- slice(start?: number, end?: number): T[];
26
- sort(compareFn?: (a: T, b: T) => number): this;
27
- splice(start: number, deleteCount?: number): T[];
28
- unshift(...items: T[]): number;
29
- indexOf(searchElement: T, fromIndex?: number): number;
30
- lastIndexOf(searchElement: T, fromIndex?: number): number;
31
- every(predicate: (value: T, index: number, array: T[]) => unknown, thisArg?: any): boolean;
32
- some(predicate: (value: T, index: number, array: T[]) => unknown, thisArg?: any): boolean;
33
- forEach(callbackfn: (value: T, index: number, array: T[]) => void, thisArg?: any): void;
34
- map<U>(callbackfn: (value: T, index: number, array: T[]) => U, thisArg?: any): U[];
35
- filter(predicate: (value: T, index: number, array: T[]) => unknown, thisArg?: any): T[];
36
- reduce(callbackfn: (previousValue: T, currentValue: T, currentIndex: number, array: T[]) => T, initialValue: T): T;
37
- reduceRight(callbackfn: (previousValue: T, currentValue: T, currentIndex: number, array: T[]) => T, initialValue: T): T;
38
- entries(): IterableIterator<[number, T]>;
39
- keys(): IterableIterator<number>;
40
- values(): IterableIterator<T>;
41
- private get mutable();
42
- }
43
- export declare class ReactiveMap<K, V> extends ReactiveObject {
44
- private m;
45
- clear(): void;
46
- delete(key: K): boolean;
47
- forEach(callbackfn: (value: V, key: K, map: Map<K, V>) => void, thisArg?: any): void;
48
- get(key: K): V | undefined;
49
- has(key: K): boolean;
50
- set(key: K, value: V): this;
51
- get size(): number;
52
- entries(): IterableIterator<[K, V]>;
53
- keys(): IterableIterator<K>;
54
- values(): IterableIterator<V>;
55
- private get mutable();
11
+ export declare abstract class ReactiveObject extends HookedObject {
12
+ constructor();
56
13
  }
57
14
  export declare class OptionsImpl implements MemberOptions {
58
15
  readonly getter: Function;
@@ -76,7 +33,10 @@ export declare class Hooks implements ProxyHandler<ObjectHandle> {
76
33
  static mainThreadBlockingWarningThreshold: number;
77
34
  static asyncActionDurationWarningThreshold: number;
78
35
  static sensitivity: boolean;
79
- static readonly handler: Hooks;
36
+ static readonly transactional: Hooks;
37
+ static readonly reactive: Hooks;
38
+ readonly isReactive: boolean;
39
+ constructor(isReactive: boolean);
80
40
  getPrototypeOf(h: ObjectHandle): object | null;
81
41
  get(h: ObjectHandle, m: MemberName, receiver: any): any;
82
42
  set(h: ObjectHandle, m: MemberName, value: any, receiver: any): boolean;
@@ -87,10 +47,11 @@ export declare class Hooks implements ProxyHandler<ObjectHandle> {
87
47
  static decorateOperation(implicit: boolean, decorator: Function, options: Partial<MemberOptions>, proto: any, member: MemberName, pd: PropertyDescriptor | undefined): any;
88
48
  static decorateOperationParametrized(decorator: Function, options: Partial<MemberOptions>): F<any>;
89
49
  static acquireHandle(obj: any): ObjectHandle;
90
- static createHandleForReactiveObject(proto: any, data: any, blank: any, hint: string): ObjectHandle;
50
+ static createHandleForReactronicObject(proto: any, data: any, blank: any, hint: string, reactive: boolean): ObjectHandle;
91
51
  static setProfilingMode(isOn: boolean, options?: Partial<ProfilingOptions>): void;
92
52
  static sensitive<T>(sensitivity: boolean, func: F<T>, ...args: any[]): T;
93
53
  static setHint<T>(obj: T, hint: string | undefined): T;
54
+ static getHint<T>(obj: T): string;
94
55
  static createOperation: (h: ObjectHandle, m: MemberName, options: OptionsImpl) => F<any>;
95
56
  static rememberOperationOptions: (proto: any, m: MemberName, getter: Function | undefined, setter: Function | undefined, enumerable: boolean, configurable: boolean, options: Partial<MemberOptions>, implicit: boolean) => OptionsImpl;
96
57
  }
@@ -1,14 +1,13 @@
1
1
  import { UNDEF } from '../util/Utils';
2
- import { Sealant } from '../util/Sealant';
3
2
  import { Log, misuse } from '../util/Dbg';
4
3
  import { Kind, Reentrance } from '../Options';
5
4
  import { ObjectSnapshot, ObjectHandle, Subscription, Meta } from './Data';
6
5
  import { Changeset, Dump, EMPTY_SNAPSHOT } from './Changeset';
7
- export class ReactiveObject {
8
- constructor() {
6
+ export class HookedObject {
7
+ constructor(reactive) {
9
8
  const proto = new.target.prototype;
10
9
  const initial = Meta.getFrom(proto, Meta.Initial);
11
- const h = Hooks.createHandleForReactiveObject(proto, this, initial, new.target.name);
10
+ const h = Hooks.createHandleForReactronicObject(proto, this, initial, new.target.name, reactive);
12
11
  return h.proxy;
13
12
  }
14
13
  [Symbol.toStringTag]() {
@@ -16,66 +15,9 @@ export class ReactiveObject {
16
15
  return Dump.obj(h);
17
16
  }
18
17
  }
19
- export class ReactiveArray extends ReactiveObject {
20
- constructor() {
21
- super(...arguments);
22
- this.a = new Array();
23
- }
24
- get length() { return this.a.length; }
25
- set length(n) { this.a.length = n; }
26
- get(n) { return this.a[n]; }
27
- set(n, item) { this.mutable[n] = item; }
28
- toString() { return this.a.toString(); }
29
- toLocaleString() { return this.a.toLocaleString(); }
30
- pop() { return this.mutable.pop(); }
31
- push(...items) { return this.mutable.push(...items); }
32
- concat(...items) { return this.a.concat(...items); }
33
- join(separator) { return this.a.join(separator); }
34
- reverse() { return this.mutable.reverse(); }
35
- shift() { return this.mutable.shift(); }
36
- slice(start, end) { return this.a.slice(start, end); }
37
- sort(compareFn) { this.mutable.sort(compareFn); return this; }
38
- splice(start, deleteCount, ...items) { return this.mutable.splice(start, deleteCount, ...items); }
39
- unshift(...items) { return this.mutable.unshift(...items); }
40
- indexOf(searchElement, fromIndex) { return this.a.indexOf(searchElement, fromIndex); }
41
- lastIndexOf(searchElement, fromIndex) { return this.a.lastIndexOf(searchElement, fromIndex); }
42
- every(predicate, thisArg) { return this.a.every(predicate, thisArg); }
43
- some(predicate, thisArg) { return this.a.some(predicate, thisArg); }
44
- forEach(callbackfn, thisArg) { return this.a.forEach(callbackfn, thisArg); }
45
- map(callbackfn, thisArg) { return this.a.map(callbackfn, thisArg); }
46
- filter(predicate, thisArg) { return this.a.filter(predicate, thisArg); }
47
- reduce(callbackfn, initialValue) { return this.a.reduce(callbackfn, initialValue); }
48
- reduceRight(callbackfn, initialValue) { return this.a.reduceRight(callbackfn, initialValue); }
49
- entries() { return this.a.entries(); }
50
- keys() { return this.a.keys(); }
51
- values() { return this.a.values(); }
52
- get mutable() {
53
- const createCopy = this.a[Sealant.CreateCopy];
54
- if (createCopy)
55
- return this.a = createCopy.call(this.a);
56
- return this.a;
57
- }
58
- }
59
- export class ReactiveMap extends ReactiveObject {
18
+ export class ReactiveObject extends HookedObject {
60
19
  constructor() {
61
- super(...arguments);
62
- this.m = new Map();
63
- }
64
- clear() { this.mutable.clear(); }
65
- delete(key) { return this.mutable.delete(key); }
66
- forEach(callbackfn, thisArg) { this.m.forEach(callbackfn, thisArg); }
67
- get(key) { return this.m.get(key); }
68
- has(key) { return this.m.has(key); }
69
- set(key, value) { this.mutable.set(key, value); return this; }
70
- get size() { return this.m.size; }
71
- entries() { return this.m.entries(); }
72
- keys() { return this.m.keys(); }
73
- values() { return this.m.values(); }
74
- get mutable() {
75
- const createCopy = this.m[Sealant.CreateCopy];
76
- if (createCopy)
77
- return this.m = createCopy.call(this.m);
78
- return this.m;
20
+ super(true);
79
21
  }
80
22
  }
81
23
  const DEFAULT_OPTIONS = Object.freeze({
@@ -113,6 +55,9 @@ function merge(def, existing, patch, implicit) {
113
55
  return patch !== undefined && (existing === def || !implicit) ? patch : existing;
114
56
  }
115
57
  export class Hooks {
58
+ constructor(isReactive) {
59
+ this.isReactive = isReactive;
60
+ }
116
61
  getPrototypeOf(h) {
117
62
  return Reflect.getPrototypeOf(h.data);
118
63
  }
@@ -120,10 +65,11 @@ export class Hooks {
120
65
  let result;
121
66
  if (m !== Meta.Handle) {
122
67
  const cs = Changeset.current();
123
- const os = cs.getRelevantSnapshot(h, m);
68
+ const os = cs.getObjectSnapshot(h, m);
124
69
  result = os.data[m];
125
70
  if (result instanceof Subscription && !result.isOperation) {
126
- Changeset.markUsed(result, os, m, h, Kind.Plain, false);
71
+ if (this.isReactive)
72
+ Changeset.markUsed(result, os, m, h, Kind.Plain, false);
127
73
  result = result.content;
128
74
  }
129
75
  else
@@ -134,7 +80,7 @@ export class Hooks {
134
80
  return result;
135
81
  }
136
82
  set(h, m, value, receiver) {
137
- const os = Changeset.edit().getEditableSnapshot(h, m, value);
83
+ const os = Changeset.edit().getEditableObjectSnapshot(h, m, value);
138
84
  if (os !== EMPTY_SNAPSHOT) {
139
85
  let curr = os.data[m];
140
86
  if (curr !== undefined || (os.former.snapshot.changeset === EMPTY_SNAPSHOT.changeset && (m in h.data) === false)) {
@@ -158,18 +104,18 @@ export class Hooks {
158
104
  return true;
159
105
  }
160
106
  has(h, m) {
161
- const os = Changeset.current().getRelevantSnapshot(h, m);
107
+ const os = Changeset.current().getObjectSnapshot(h, m);
162
108
  return m in os.data || m in h.data;
163
109
  }
164
110
  getOwnPropertyDescriptor(h, m) {
165
- const os = Changeset.current().getRelevantSnapshot(h, m);
111
+ const os = Changeset.current().getObjectSnapshot(h, m);
166
112
  const pd = Reflect.getOwnPropertyDescriptor(os.data, m);
167
113
  if (pd)
168
114
  pd.configurable = pd.writable = true;
169
115
  return pd;
170
116
  }
171
117
  ownKeys(h) {
172
- const os = Changeset.current().getRelevantSnapshot(h, Meta.Handle);
118
+ const os = Changeset.current().getObjectSnapshot(h, Meta.Handle);
173
119
  const result = [];
174
120
  for (const m of Object.getOwnPropertyNames(os.data)) {
175
121
  const value = os.data[m];
@@ -182,11 +128,11 @@ export class Hooks {
182
128
  if (reactive) {
183
129
  const get = function () {
184
130
  const h = Hooks.acquireHandle(this);
185
- return Hooks.handler.get(h, m, this);
131
+ return Hooks.reactive.get(h, m, this);
186
132
  };
187
133
  const set = function (value) {
188
134
  const h = Hooks.acquireHandle(this);
189
- return Hooks.handler.set(h, m, value, this);
135
+ return Hooks.reactive.set(h, m, value, this);
190
136
  };
191
137
  const enumerable = true;
192
138
  const configurable = false;
@@ -235,17 +181,18 @@ export class Hooks {
235
181
  throw misuse('only objects can be reactive');
236
182
  const initial = Meta.getFrom(Object.getPrototypeOf(obj), Meta.Initial);
237
183
  const os = new ObjectSnapshot(EMPTY_SNAPSHOT.changeset, EMPTY_SNAPSHOT, Object.assign({}, initial));
238
- h = new ObjectHandle(obj, obj, Hooks.handler, os, obj.constructor.name);
184
+ h = new ObjectHandle(obj, obj, Hooks.reactive, os, obj.constructor.name);
239
185
  Meta.set(os.data, Meta.Handle, h);
240
186
  Meta.set(obj, Meta.Handle, h);
241
187
  Meta.set(os.data, Meta.Revision, new Subscription(1));
242
188
  }
243
189
  return h;
244
190
  }
245
- static createHandleForReactiveObject(proto, data, blank, hint) {
191
+ static createHandleForReactronicObject(proto, data, blank, hint, reactive) {
246
192
  const ctx = Changeset.edit();
247
- const h = new ObjectHandle(data, undefined, Hooks.handler, EMPTY_SNAPSHOT, hint);
248
- ctx.getEditableSnapshot(h, Meta.Handle, blank);
193
+ const hooks = reactive ? Hooks.reactive : Hooks.transactional;
194
+ const h = new ObjectHandle(data, undefined, hooks, EMPTY_SNAPSHOT, hint);
195
+ ctx.getEditableObjectSnapshot(h, Meta.Handle, blank);
249
196
  if (!Hooks.reactionsAutoStartDisabled)
250
197
  for (const m in Meta.getFrom(proto, Meta.Reactions))
251
198
  h.proxy[m][Meta.Controller].markObsolete();
@@ -282,13 +229,18 @@ export class Hooks {
282
229
  }
283
230
  return obj;
284
231
  }
232
+ static getHint(obj) {
233
+ const h = Hooks.acquireHandle(obj);
234
+ return h.hint;
235
+ }
285
236
  }
286
237
  Hooks.reactionsAutoStartDisabled = false;
287
238
  Hooks.repetitiveUsageWarningThreshold = Number.MAX_SAFE_INTEGER;
288
239
  Hooks.mainThreadBlockingWarningThreshold = Number.MAX_SAFE_INTEGER;
289
240
  Hooks.asyncActionDurationWarningThreshold = Number.MAX_SAFE_INTEGER;
290
241
  Hooks.sensitivity = false;
291
- Hooks.handler = new Hooks();
242
+ Hooks.transactional = new Hooks(false);
243
+ Hooks.reactive = new Hooks(true);
292
244
  Hooks.createOperation = function (h, m, options) {
293
245
  throw misuse('createOperation should never be called');
294
246
  };
@@ -97,7 +97,7 @@ export class JournalImpl extends Journal {
97
97
  if (!disposed) {
98
98
  op.forEach((vp, m) => {
99
99
  const value = undoing ? vp.formerValue : vp.freshValue;
100
- const os = ctx.getEditableSnapshot(h, m, value);
100
+ const os = ctx.getEditableObjectSnapshot(h, m, value);
101
101
  if (os.changeset === ctx) {
102
102
  os.data[m] = new Subscription(value);
103
103
  const existing = os.former.snapshot.data[m];
@@ -12,7 +12,7 @@ export declare class MonitorImpl extends Monitor {
12
12
  counter: number;
13
13
  workers: Set<Worker>;
14
14
  duration: number;
15
- internals: {
15
+ readonly internals: {
16
16
  started: number;
17
17
  activationDelay: number;
18
18
  activationTimeout: undefined;
@@ -51,7 +51,8 @@ export class MonitorImpl extends Monitor {
51
51
  return m;
52
52
  }
53
53
  static activate(mon, delay) {
54
- if (mon.internals.started === 0) {
54
+ const active = mon.counter > 0;
55
+ if (mon.internals.started === 0 && active) {
55
56
  mon.duration = 0;
56
57
  mon.internals.started = performance.now();
57
58
  MonitorImpl.tick(mon);
@@ -60,7 +61,7 @@ export class MonitorImpl extends Monitor {
60
61
  if (mon.internals.activationTimeout === undefined)
61
62
  mon.internals.activationTimeout = setTimeout(() => Transaction.run({ hint: 'Monitor.activate', standalone: 'isolated' }, MonitorImpl.activate, mon, -1), delay);
62
63
  }
63
- else if (mon.counter > 0)
64
+ else if (active)
64
65
  mon.isActive = true;
65
66
  }
66
67
  static deactivate(mon, delay) {
@@ -80,7 +81,7 @@ export class MonitorImpl extends Monitor {
80
81
  }
81
82
  static tick(mon) {
82
83
  if (mon.internals.started !== 0) {
83
- Transaction.run(null, () => {
84
+ Transaction.run(MONITOR_TICK_OPTIONS, () => {
84
85
  const resolution = mon.internals.durationResolution;
85
86
  mon.duration = Math.round(resolution * (performance.now() - mon.internals.started)) / resolution;
86
87
  });
@@ -90,3 +91,6 @@ export class MonitorImpl extends Monitor {
90
91
  }
91
92
  }
92
93
  }
94
+ const MONITOR_TICK_OPTIONS = Object.freeze({
95
+ hint: 'Monitor.tick',
96
+ });
@@ -9,7 +9,7 @@ import { Hooks, OptionsImpl } from './Hooks';
9
9
  import { JournalImpl } from './Journal';
10
10
  const BOOT_ARGS = [];
11
11
  const BOOT_CAUSE = '<boot>';
12
- const EMPTY_HANDLE = new ObjectHandle(undefined, undefined, Hooks.handler, EMPTY_SNAPSHOT, '<empty>');
12
+ const EMPTY_HANDLE = new ObjectHandle(undefined, undefined, Hooks.reactive, EMPTY_SNAPSHOT, '<empty>');
13
13
  export class OperationController extends Controller {
14
14
  constructor(h, m) {
15
15
  super();
@@ -103,7 +103,7 @@ export class OperationController extends Controller {
103
103
  }
104
104
  peek(args) {
105
105
  const ctx = Changeset.current();
106
- const os = ctx.seekSnapshot(this.objectHandle, this.memberName);
106
+ const os = ctx.lookupObjectSnapshot(this.objectHandle, this.memberName);
107
107
  const op = this.acquireFromSnapshot(os, args);
108
108
  const isValid = op.options.kind !== Kind.Transaction && op.cause !== BOOT_CAUSE &&
109
109
  (ctx === op.changeset || ctx.timestamp < op.obsoleteSince) &&
@@ -120,7 +120,7 @@ export class OperationController extends Controller {
120
120
  const h = this.objectHandle;
121
121
  const m = this.memberName;
122
122
  const ctx = Changeset.edit();
123
- const os = ctx.getEditableSnapshot(h, m, Meta.Handle, this);
123
+ const os = ctx.getEditableObjectSnapshot(h, m, Meta.Handle, this);
124
124
  let op = this.acquireFromSnapshot(os, undefined);
125
125
  if (op.changeset !== os.changeset) {
126
126
  const op2 = new Operation(this, os.changeset, op);
@@ -140,10 +140,10 @@ export class OperationController extends Controller {
140
140
  const standalone = os.changeset.sealed || os.former.snapshot !== EMPTY_SNAPSHOT;
141
141
  op = Transaction.run({ hint, standalone, token: this }, () => {
142
142
  const h = this.objectHandle;
143
- let r2 = Changeset.current().getRelevantSnapshot(h, m);
143
+ let r2 = Changeset.current().getObjectSnapshot(h, m);
144
144
  let op2 = r2.data[m];
145
145
  if (op2.controller !== this) {
146
- r2 = Changeset.edit().getEditableSnapshot(h, m, Meta.Handle, this);
146
+ r2 = Changeset.edit().getEditableObjectSnapshot(h, m, Meta.Handle, this);
147
147
  const t = new Operation(this, r2.changeset, op2);
148
148
  if (args)
149
149
  t.args = args;
@@ -0,0 +1,33 @@
1
+ import { ReactiveObject } from './Hooks';
2
+ export declare class ReactiveArray<T> extends ReactiveObject {
3
+ private a;
4
+ get length(): number;
5
+ set length(n: number);
6
+ get(n: number): T;
7
+ set(n: number, item: T): void;
8
+ toString(): string;
9
+ toLocaleString(): string;
10
+ pop(): T | undefined;
11
+ push(...items: T[]): number;
12
+ concat(...items: (T | ConcatArray<T>)[]): T[];
13
+ join(separator?: string): string;
14
+ reverse(): T[];
15
+ shift(): T | undefined;
16
+ slice(start?: number, end?: number): T[];
17
+ sort(compareFn?: (a: T, b: T) => number): this;
18
+ splice(start: number, deleteCount?: number): T[];
19
+ unshift(...items: T[]): number;
20
+ indexOf(searchElement: T, fromIndex?: number): number;
21
+ lastIndexOf(searchElement: T, fromIndex?: number): number;
22
+ every(predicate: (value: T, index: number, array: T[]) => unknown, thisArg?: any): boolean;
23
+ some(predicate: (value: T, index: number, array: T[]) => unknown, thisArg?: any): boolean;
24
+ forEach(callbackfn: (value: T, index: number, array: T[]) => void, thisArg?: any): void;
25
+ map<U>(callbackfn: (value: T, index: number, array: T[]) => U, thisArg?: any): U[];
26
+ filter(predicate: (value: T, index: number, array: T[]) => unknown, thisArg?: any): T[];
27
+ reduce(callbackfn: (previousValue: T, currentValue: T, currentIndex: number, array: T[]) => T, initialValue: T): T;
28
+ reduceRight(callbackfn: (previousValue: T, currentValue: T, currentIndex: number, array: T[]) => T, initialValue: T): T;
29
+ entries(): IterableIterator<[number, T]>;
30
+ keys(): IterableIterator<number>;
31
+ values(): IterableIterator<T>;
32
+ private get mutable();
33
+ }
@@ -0,0 +1,42 @@
1
+ import { Sealant } from '../util/Sealant';
2
+ import { ReactiveObject } from './Hooks';
3
+ export class ReactiveArray extends ReactiveObject {
4
+ constructor() {
5
+ super(...arguments);
6
+ this.a = new Array();
7
+ }
8
+ get length() { return this.a.length; }
9
+ set length(n) { this.a.length = n; }
10
+ get(n) { return this.a[n]; }
11
+ set(n, item) { this.mutable[n] = item; }
12
+ toString() { return this.a.toString(); }
13
+ toLocaleString() { return this.a.toLocaleString(); }
14
+ pop() { return this.mutable.pop(); }
15
+ push(...items) { return this.mutable.push(...items); }
16
+ concat(...items) { return this.a.concat(...items); }
17
+ join(separator) { return this.a.join(separator); }
18
+ reverse() { return this.mutable.reverse(); }
19
+ shift() { return this.mutable.shift(); }
20
+ slice(start, end) { return this.a.slice(start, end); }
21
+ sort(compareFn) { this.mutable.sort(compareFn); return this; }
22
+ splice(start, deleteCount, ...items) { return this.mutable.splice(start, deleteCount, ...items); }
23
+ unshift(...items) { return this.mutable.unshift(...items); }
24
+ indexOf(searchElement, fromIndex) { return this.a.indexOf(searchElement, fromIndex); }
25
+ lastIndexOf(searchElement, fromIndex) { return this.a.lastIndexOf(searchElement, fromIndex); }
26
+ every(predicate, thisArg) { return this.a.every(predicate, thisArg); }
27
+ some(predicate, thisArg) { return this.a.some(predicate, thisArg); }
28
+ forEach(callbackfn, thisArg) { return this.a.forEach(callbackfn, thisArg); }
29
+ map(callbackfn, thisArg) { return this.a.map(callbackfn, thisArg); }
30
+ filter(predicate, thisArg) { return this.a.filter(predicate, thisArg); }
31
+ reduce(callbackfn, initialValue) { return this.a.reduce(callbackfn, initialValue); }
32
+ reduceRight(callbackfn, initialValue) { return this.a.reduceRight(callbackfn, initialValue); }
33
+ entries() { return this.a.entries(); }
34
+ keys() { return this.a.keys(); }
35
+ values() { return this.a.values(); }
36
+ get mutable() {
37
+ const createCopy = this.a[Sealant.CreateCopy];
38
+ if (createCopy)
39
+ return this.a = createCopy.call(this.a);
40
+ return this.a;
41
+ }
42
+ }
@@ -0,0 +1,15 @@
1
+ import { ReactiveObject } from './Hooks';
2
+ export declare class ReactiveMap<K, V> extends ReactiveObject {
3
+ private m;
4
+ clear(): void;
5
+ delete(key: K): boolean;
6
+ forEach(callbackfn: (value: V, key: K, map: Map<K, V>) => void, thisArg?: any): void;
7
+ get(key: K): V | undefined;
8
+ has(key: K): boolean;
9
+ set(key: K, value: V): this;
10
+ get size(): number;
11
+ entries(): IterableIterator<[K, V]>;
12
+ keys(): IterableIterator<K>;
13
+ values(): IterableIterator<V>;
14
+ private get mutable();
15
+ }
@@ -0,0 +1,24 @@
1
+ import { Sealant } from '../util/Sealant';
2
+ import { ReactiveObject } from './Hooks';
3
+ export class ReactiveMap extends ReactiveObject {
4
+ constructor() {
5
+ super(...arguments);
6
+ this.m = new Map();
7
+ }
8
+ clear() { this.mutable.clear(); }
9
+ delete(key) { return this.mutable.delete(key); }
10
+ forEach(callbackfn, thisArg) { this.m.forEach(callbackfn, thisArg); }
11
+ get(key) { return this.m.get(key); }
12
+ has(key) { return this.m.has(key); }
13
+ set(key, value) { this.mutable.set(key, value); return this; }
14
+ get size() { return this.m.size; }
15
+ entries() { return this.m.entries(); }
16
+ keys() { return this.m.keys(); }
17
+ values() { return this.m.values(); }
18
+ get mutable() {
19
+ const createCopy = this.m[Sealant.CreateCopy];
20
+ if (createCopy)
21
+ return this.m = createCopy.call(this.m);
22
+ return this.m;
23
+ }
24
+ }
@@ -301,7 +301,7 @@ class TransactionImpl extends Transaction {
301
301
  Changeset._init();
302
302
  }
303
303
  }
304
- TransactionImpl.none = new TransactionImpl({ hint: 'Transaction.off' });
304
+ TransactionImpl.none = new TransactionImpl({ hint: '<none>' });
305
305
  TransactionImpl.curr = TransactionImpl.none;
306
306
  TransactionImpl.inspection = false;
307
307
  TransactionImpl.frameStartTime = 0;
@@ -0,0 +1,61 @@
1
+ export declare type GetItemKey<T = unknown> = (item: T) => string | undefined;
2
+ export interface CollectionReader<T> {
3
+ readonly strict: boolean;
4
+ readonly count: number;
5
+ readonly addedCount: number;
6
+ readonly removedCount: number;
7
+ readonly isMergeInProgress: boolean;
8
+ lookup(key: string): Item<T> | undefined;
9
+ claim(key: string): Item<T> | undefined;
10
+ add(self: T): Item<T>;
11
+ remove(item: Item<T>): void;
12
+ move(item: Item<T>, after: Item<T>): void;
13
+ beginMerge(): void;
14
+ endMerge(error?: unknown): void;
15
+ resetAddedAndRemovedLists(): void;
16
+ items(): Generator<Item<T>>;
17
+ addedItems(reset?: boolean): Generator<Item<T>>;
18
+ removedItems(reset?: boolean): Generator<Item<T>>;
19
+ isAdded(item: Item<T>): boolean;
20
+ isMoved(item: Item<T>): boolean;
21
+ isRemoved(item: Item<T>): boolean;
22
+ isCurrent(item: Item<T>): boolean;
23
+ }
24
+ export interface Item<T> {
25
+ readonly self: T;
26
+ readonly prev?: Item<T>;
27
+ aux?: Item<T>;
28
+ }
29
+ export declare class Collection<T> implements CollectionReader<T> {
30
+ readonly strict: boolean;
31
+ readonly getKey: GetItemKey<T>;
32
+ private map;
33
+ private tag;
34
+ private current;
35
+ private added;
36
+ private removed;
37
+ private lastNotFoundKey;
38
+ private strictNextItem?;
39
+ constructor(strict: boolean, getKey: GetItemKey<T>);
40
+ get count(): number;
41
+ get addedCount(): number;
42
+ get removedCount(): number;
43
+ get isMergeInProgress(): boolean;
44
+ lookup(key: string | undefined): Item<T> | undefined;
45
+ claim(key: string): Item<T> | undefined;
46
+ add(self: T): Item<T>;
47
+ remove(item: Item<T>): void;
48
+ move(item: Item<T>, after: Item<T>): void;
49
+ beginMerge(): void;
50
+ endMerge(error?: unknown): void;
51
+ resetAddedAndRemovedLists(): void;
52
+ items(): Generator<Item<T>>;
53
+ addedItems(reset?: boolean): Generator<Item<T>>;
54
+ removedItems(reset?: boolean): Generator<Item<T>>;
55
+ isAdded(item: Item<T>): boolean;
56
+ isMoved(item: Item<T>): boolean;
57
+ isRemoved(item: Item<T>): boolean;
58
+ isCurrent(item: Item<T>): boolean;
59
+ markAsMoved(item: Item<T>): void;
60
+ static createItem<T>(self: T): Item<T>;
61
+ }
@@ -0,0 +1,275 @@
1
+ export class Collection {
2
+ constructor(strict, getKey) {
3
+ this.strict = strict;
4
+ this.getKey = getKey;
5
+ this.map = new Map();
6
+ this.tag = ~0;
7
+ this.current = new ItemChain();
8
+ this.added = new ItemChain();
9
+ this.removed = new ItemChain();
10
+ this.lastNotFoundKey = undefined;
11
+ this.strictNextItem = undefined;
12
+ }
13
+ get count() {
14
+ return this.current.count;
15
+ }
16
+ get addedCount() {
17
+ return this.added.count;
18
+ }
19
+ get removedCount() {
20
+ return this.removed.count;
21
+ }
22
+ get isMergeInProgress() {
23
+ return this.tag > 0;
24
+ }
25
+ lookup(key) {
26
+ let result = undefined;
27
+ if (key !== undefined && key !== this.lastNotFoundKey) {
28
+ result = this.map.get(key);
29
+ if (result) {
30
+ if (this.getKey(result.self) !== key) {
31
+ this.lastNotFoundKey = key;
32
+ result = undefined;
33
+ }
34
+ }
35
+ else
36
+ this.lastNotFoundKey = key;
37
+ }
38
+ return result;
39
+ }
40
+ claim(key) {
41
+ const tag = this.tag;
42
+ if (tag < 0)
43
+ throw new Error('merge is not in progress');
44
+ let item = this.strictNextItem;
45
+ if (key !== (item ? this.getKey(item.self) : undefined))
46
+ item = this.lookup(key);
47
+ if (item) {
48
+ if (item.tag === tag)
49
+ throw new Error(`duplicate item: ${key}`);
50
+ item.tag = tag;
51
+ if (this.strict && item !== this.strictNextItem)
52
+ item.status = tag;
53
+ this.strictNextItem = item.next;
54
+ this.removed.exclude(item);
55
+ this.current.include(item);
56
+ }
57
+ return item;
58
+ }
59
+ add(self) {
60
+ const key = this.getKey(self);
61
+ if (this.lookup(key) !== undefined)
62
+ throw new Error(`key is already in use: ${key}`);
63
+ let tag = this.tag;
64
+ if (tag < 0) {
65
+ tag = ~this.tag + 1;
66
+ this.tag = ~tag;
67
+ }
68
+ const item = new ItemImpl(self, tag);
69
+ this.map.set(key, item);
70
+ this.lastNotFoundKey = undefined;
71
+ this.strictNextItem = undefined;
72
+ this.current.include(item);
73
+ this.added.aux(item);
74
+ return item;
75
+ }
76
+ remove(item) {
77
+ const t = item;
78
+ if (!this.isRemoved(t)) {
79
+ this.current.exclude(t);
80
+ this.removed.include(t);
81
+ t.tag--;
82
+ }
83
+ }
84
+ move(item, after) {
85
+ throw new Error('not implemented');
86
+ }
87
+ beginMerge() {
88
+ if (this.isMergeInProgress)
89
+ throw new Error('merge is in progress already');
90
+ this.tag = ~this.tag + 1;
91
+ this.strictNextItem = this.current.first;
92
+ this.removed.grab(this.current, false);
93
+ this.added.reset();
94
+ }
95
+ endMerge(error) {
96
+ if (!this.isMergeInProgress)
97
+ throw new Error('merge is ended already');
98
+ this.tag = ~this.tag;
99
+ if (error === undefined) {
100
+ const currentCount = this.current.count;
101
+ if (currentCount > 0) {
102
+ const getKey = this.getKey;
103
+ if (currentCount > this.removed.count) {
104
+ const map = this.map;
105
+ for (const x of this.removed.items())
106
+ map.delete(getKey(x.self));
107
+ }
108
+ else {
109
+ const map = this.map = new Map();
110
+ for (const x of this.current.items())
111
+ map.set(getKey(x.self), x);
112
+ }
113
+ }
114
+ else
115
+ this.map = new Map();
116
+ }
117
+ else {
118
+ this.current.grab(this.removed, true);
119
+ const getKey = this.getKey;
120
+ for (const x of this.added.itemsViaAux()) {
121
+ this.map.delete(getKey(x.self));
122
+ this.current.exclude(x);
123
+ }
124
+ this.added.reset();
125
+ }
126
+ }
127
+ resetAddedAndRemovedLists() {
128
+ this.removed.reset();
129
+ this.added.reset();
130
+ }
131
+ *items() {
132
+ let x = this.current.first;
133
+ while (x !== undefined) {
134
+ const next = x.next;
135
+ yield x;
136
+ x = next;
137
+ }
138
+ }
139
+ *addedItems(reset) {
140
+ let x = this.added.first;
141
+ while (x !== undefined) {
142
+ const next = x.aux;
143
+ if (!this.isRemoved(x))
144
+ yield x;
145
+ x = next;
146
+ }
147
+ if (reset)
148
+ this.added.reset();
149
+ }
150
+ *removedItems(reset) {
151
+ let x = this.removed.first;
152
+ while (x !== undefined) {
153
+ const next = x.next;
154
+ yield x;
155
+ x = next;
156
+ }
157
+ if (reset)
158
+ this.removed.reset();
159
+ }
160
+ isAdded(item) {
161
+ const t = item;
162
+ let tag = this.tag;
163
+ if (tag < 0)
164
+ tag = ~tag;
165
+ return t.status === ~tag && t.tag > 0;
166
+ }
167
+ isMoved(item) {
168
+ const t = item;
169
+ let tag = this.tag;
170
+ if (tag < 0)
171
+ tag = ~tag;
172
+ return t.status === tag && t.tag > 0;
173
+ }
174
+ isRemoved(item) {
175
+ const t = item;
176
+ const tag = this.tag;
177
+ return tag > 0 ? t.tag < tag : t.tag < tag - 1;
178
+ }
179
+ isCurrent(item) {
180
+ const t = item;
181
+ return t.tag === this.tag;
182
+ }
183
+ markAsMoved(item) {
184
+ const t = item;
185
+ if (t.tag > 0)
186
+ t.status = t.tag;
187
+ }
188
+ static createItem(self) {
189
+ return new ItemImpl(self, 0);
190
+ }
191
+ }
192
+ class ItemImpl {
193
+ constructor(self, tag) {
194
+ this.self = self;
195
+ this.tag = tag;
196
+ this.status = ~tag;
197
+ this.next = undefined;
198
+ this.prev = undefined;
199
+ this.aux = undefined;
200
+ }
201
+ }
202
+ class ItemChain {
203
+ constructor() {
204
+ this.count = 0;
205
+ this.first = undefined;
206
+ this.last = undefined;
207
+ }
208
+ *items() {
209
+ let x = this.first;
210
+ while (x !== undefined) {
211
+ const next = x.next;
212
+ yield x;
213
+ x = next;
214
+ }
215
+ }
216
+ *itemsViaAux() {
217
+ let x = this.first;
218
+ while (x !== undefined) {
219
+ const next = x.aux;
220
+ yield x;
221
+ x = next;
222
+ }
223
+ }
224
+ reset() {
225
+ this.count = 0;
226
+ this.first = undefined;
227
+ this.last = undefined;
228
+ }
229
+ grab(from, join) {
230
+ const head = from.first;
231
+ if (join && head) {
232
+ const last = this.last;
233
+ head.prev = last;
234
+ if (last)
235
+ this.last = last.next = head;
236
+ else
237
+ this.first = this.last = head;
238
+ this.count += from.count;
239
+ }
240
+ else {
241
+ this.count = from.count;
242
+ this.first = head;
243
+ this.last = from.last;
244
+ }
245
+ from.reset();
246
+ }
247
+ include(item) {
248
+ const last = this.last;
249
+ item.prev = last;
250
+ item.next = undefined;
251
+ if (last)
252
+ this.last = last.next = item;
253
+ else
254
+ this.first = this.last = item;
255
+ this.count++;
256
+ }
257
+ exclude(item) {
258
+ if (item.prev !== undefined)
259
+ item.prev.next = item.next;
260
+ if (item.next !== undefined)
261
+ item.next.prev = item.prev;
262
+ if (item === this.first)
263
+ this.first = item.next;
264
+ this.count--;
265
+ }
266
+ aux(item) {
267
+ item.aux = undefined;
268
+ const last = this.last;
269
+ if (last)
270
+ this.last = last.aux = item;
271
+ else
272
+ this.first = this.last = item;
273
+ this.count++;
274
+ }
275
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "reactronic",
3
- "version": "0.22.311",
3
+ "version": "0.22.314",
4
4
  "description": "Reactronic - Transactional Reactive State Management",
5
5
  "type": "module",
6
6
  "main": "build/dist/source/api.js",
@@ -30,15 +30,15 @@
30
30
  },
31
31
  "homepage": "https://github.com/nezaboodka/reactronic/blob/master/README.md#readme",
32
32
  "devDependencies": {
33
- "@types/node": "18.0.0",
34
- "@types/react": "18.0.14",
35
- "@typescript-eslint/eslint-plugin": "5.29.0",
36
- "@typescript-eslint/parser": "5.29.0",
37
- "ava": "4.3.0",
33
+ "@types/node": "18.0.5",
34
+ "@types/react": "18.0.15",
35
+ "@typescript-eslint/eslint-plugin": "5.30.6",
36
+ "@typescript-eslint/parser": "5.30.6",
37
+ "ava": "4.3.1",
38
38
  "c8": "7.11.3",
39
- "eslint": "8.18.0",
39
+ "eslint": "8.19.0",
40
40
  "react": "18.2.0",
41
- "ts-node": "10.8.1",
41
+ "ts-node": "10.9.1",
42
42
  "typescript": "4.7.3"
43
43
  },
44
44
  "scripts": {