reactronic 0.91.25002 → 0.92.25004
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 +68 -61
- package/build/dist/source/Clock.js +3 -3
- package/build/dist/source/Options.d.ts +3 -3
- package/build/dist/source/Options.js +3 -3
- package/build/dist/source/Reaction.js +2 -2
- package/build/dist/source/ReactiveSystem.d.ts +9 -8
- package/build/dist/source/ReactiveSystem.js +30 -19
- package/build/dist/source/Ref.js +3 -4
- package/build/dist/source/api.d.ts +1 -1
- package/build/dist/source/api.js +1 -1
- package/build/dist/source/core/Operation.d.ts +1 -1
- package/build/dist/source/core/Operation.js +12 -12
- package/build/dist/source/core/ReactiveNode.js +11 -11
- package/build/dist/source/util/MergeList.d.ts +0 -7
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -17,18 +17,18 @@ Transactional reactivity means that state changes are being made in an
|
|
|
17
17
|
isolated data snapshot and then, once atomically applied, are
|
|
18
18
|
**consistently propagated** to corresponding visual components for
|
|
19
19
|
(re)rendering. All that is done in automatic, seamless, and fine-grained
|
|
20
|
-
way
|
|
20
|
+
way. Reactronic **takes full care of tracking dependencies**
|
|
21
21
|
between visual components (observers) and state (observable objects).
|
|
22
22
|
|
|
23
23
|
Transactional reactivity is based on four fundamental concepts:
|
|
24
24
|
|
|
25
25
|
- **Observable Objects** - a set of objects that store data of an
|
|
26
26
|
application (state);
|
|
27
|
-
- **
|
|
27
|
+
- **Applying Function** - it makes changes in observable
|
|
28
28
|
objects in atomic way ("all or nothing");
|
|
29
|
-
- **
|
|
29
|
+
- **Reaction Function** - it is executed automatically in
|
|
30
30
|
response to changes made by a transaction;
|
|
31
|
-
- **
|
|
31
|
+
- **Cache Function** - its result is remembered and, if the becomes
|
|
32
32
|
obsolete, recomputed on-demand.
|
|
33
33
|
|
|
34
34
|
Demo application built with Reactronic: https://nevod.io/#/playground.
|
|
@@ -45,13 +45,13 @@ class Demo extends ObservableObject {
|
|
|
45
45
|
name: string = 'Nezaboodka Software'
|
|
46
46
|
email: string = 'contact@nezaboodka.com'
|
|
47
47
|
|
|
48
|
-
@
|
|
48
|
+
@apply
|
|
49
49
|
saveContact(name: string, email: string): void {
|
|
50
50
|
this.name = name
|
|
51
51
|
this.email = email
|
|
52
52
|
}
|
|
53
53
|
|
|
54
|
-
@
|
|
54
|
+
@reaction
|
|
55
55
|
printContact(): void {
|
|
56
56
|
// depends on `name` and `email` and reacts to their changes
|
|
57
57
|
if (this.email.indexOf('@') >= 0)
|
|
@@ -61,23 +61,27 @@ class Demo extends ObservableObject {
|
|
|
61
61
|
}
|
|
62
62
|
```
|
|
63
63
|
|
|
64
|
-
In the example above, `
|
|
65
|
-
|
|
66
|
-
to
|
|
64
|
+
In the example above, `Demo` is an observable object,
|
|
65
|
+
meaning that access to its fields are seamlessly tracked
|
|
66
|
+
to determine dependent reactions and caches. Reaction
|
|
67
|
+
function `printContact` reads `name` and `email` fields,
|
|
68
|
+
thus depends on them. It is executed automatically in
|
|
69
|
+
response to changes of these fields made by the applying
|
|
70
|
+
function `saveContact`.
|
|
67
71
|
|
|
68
|
-
Here is an example of cached value (re-)computed on-demand:
|
|
72
|
+
Here is an example of a cached value (re-)computed on-demand:
|
|
69
73
|
|
|
70
74
|
``` typescript
|
|
71
75
|
class Demo extends ObservableObject {
|
|
72
76
|
name: string = 'Nezaboodka Software'
|
|
73
77
|
email: string = 'contact@nezaboodka.com'
|
|
74
78
|
|
|
75
|
-
@
|
|
79
|
+
@cache
|
|
76
80
|
get contact(): string {
|
|
77
81
|
return this.name + ' <' + this.email + '>'
|
|
78
82
|
}
|
|
79
83
|
|
|
80
|
-
@
|
|
84
|
+
@reaction
|
|
81
85
|
printContact(): void {
|
|
82
86
|
if (this.contact !== '')
|
|
83
87
|
Console.log(this.contact)
|
|
@@ -90,7 +94,7 @@ source fields `name` and `email` upon first use. Once computed,
|
|
|
90
94
|
the result is cached and is reused until source fields `name`
|
|
91
95
|
and `email` are changed. Once source fields changed, `contact`
|
|
92
96
|
value becomes obsolete, thus causing execution of depending
|
|
93
|
-
|
|
97
|
+
reaction function `printContact`. When `printContact` function
|
|
94
98
|
runs it reads `contact` and causes its re-computation.
|
|
95
99
|
|
|
96
100
|
## Observable Objects
|
|
@@ -111,17 +115,19 @@ In the example above, the class `MyModel` is based on Reactronic's
|
|
|
111
115
|
`ObservableObject` class and all its properties `url`, `content`,
|
|
112
116
|
and `timestamp` are hooked.
|
|
113
117
|
|
|
114
|
-
##
|
|
118
|
+
## Apply
|
|
115
119
|
|
|
116
|
-
|
|
117
|
-
in transactional (atomic) way
|
|
118
|
-
|
|
120
|
+
Applying function makes changes in observable objects
|
|
121
|
+
in transactional (atomic) way, thus provoking execution
|
|
122
|
+
of dependent reactions and recalculation of dependent
|
|
123
|
+
caches. Applying function is instrumented with hooks to
|
|
124
|
+
provide transparent atomicity (by implicit context
|
|
119
125
|
switching and isolation).
|
|
120
126
|
|
|
121
127
|
``` typescript
|
|
122
128
|
class MyModel extends ObservableObject {
|
|
123
129
|
// ...
|
|
124
|
-
@
|
|
130
|
+
@apply
|
|
125
131
|
async load(url: string): Promise<void> {
|
|
126
132
|
this.url = url
|
|
127
133
|
this.content = await fetch(url)
|
|
@@ -130,9 +136,9 @@ class MyModel extends ObservableObject {
|
|
|
130
136
|
}
|
|
131
137
|
```
|
|
132
138
|
|
|
133
|
-
In the example above, the
|
|
139
|
+
In the example above, the applying function `load` makes
|
|
134
140
|
changes to `url`, `content` and `timestamp` properties. While
|
|
135
|
-
transaction is running, the changes are visible only inside the
|
|
141
|
+
applying transaction is running, the changes are visible only inside the
|
|
136
142
|
transaction itself. The new values become atomically visible outside
|
|
137
143
|
of the transaction only upon its completion.
|
|
138
144
|
|
|
@@ -159,21 +165,21 @@ all of them. An asynchronous call may spawn other asynchronous
|
|
|
159
165
|
calls, which prolong transaction execution until the whole chain
|
|
160
166
|
of asynchronous operations is fully completed.
|
|
161
167
|
|
|
162
|
-
##
|
|
168
|
+
## Reaction & Cache
|
|
163
169
|
|
|
164
|
-
|
|
165
|
-
response to changes made by
|
|
166
|
-
|
|
167
|
-
marked as obsolete due to changes made by
|
|
168
|
-
|
|
170
|
+
Reaction function is automatically and immediately called in
|
|
171
|
+
response to changes in observable objects made by an applying function.
|
|
172
|
+
Cache function is called on-demand to renew the value if it was
|
|
173
|
+
marked as obsolete due to changes made by an applying function.
|
|
174
|
+
Reaction and cache functions are instrumented with hooks
|
|
169
175
|
to seamlessly subscribe to those observable objects and
|
|
170
|
-
other
|
|
176
|
+
other cache functions (dependencies), which are used
|
|
171
177
|
during their execution.
|
|
172
178
|
|
|
173
179
|
``` tsx
|
|
174
180
|
class MyView extends Component<{model: MyModel}> {
|
|
175
|
-
@
|
|
176
|
-
render(): JSX.Element {
|
|
181
|
+
@cache
|
|
182
|
+
render(): React.JSX.Element {
|
|
177
183
|
return (
|
|
178
184
|
<div>
|
|
179
185
|
<h1>{this.props.model.url}</h1>
|
|
@@ -186,12 +192,12 @@ class MyView extends Component<{model: MyModel}> {
|
|
|
186
192
|
|
|
187
193
|
``` tsx
|
|
188
194
|
class Component<P> extends React.Component<P> {
|
|
189
|
-
@
|
|
190
|
-
render(): JSX.Element {
|
|
195
|
+
@cache
|
|
196
|
+
render(): React.JSX.Element {
|
|
191
197
|
throw new Error('render method is undefined')
|
|
192
198
|
}
|
|
193
199
|
|
|
194
|
-
@
|
|
200
|
+
@reaction // called immediately in response to changes
|
|
195
201
|
ensureUpToDate(): void {
|
|
196
202
|
if (this.shouldComponentUpdate())
|
|
197
203
|
Transaction.outside(() => this.setState({})) // ask React to re-render
|
|
@@ -206,29 +212,29 @@ class Component<P> extends React.Component<P> {
|
|
|
206
212
|
}
|
|
207
213
|
|
|
208
214
|
componentWillUnmount(): void {
|
|
209
|
-
|
|
215
|
+
apply(RxSystem.dispose, this)
|
|
210
216
|
}
|
|
211
217
|
}
|
|
212
218
|
```
|
|
213
219
|
|
|
214
|
-
In the example above,
|
|
215
|
-
to the
|
|
216
|
-
subscribed to the `url` and `content`
|
|
220
|
+
In the example above, reaction function `refresh` is transparently subscribed
|
|
221
|
+
to the cache function `render`. In turn, cache function `render` is
|
|
222
|
+
subscribed to the properties `url` and `content` of a corresponding
|
|
217
223
|
`MyModel` object. Once `url` or `content` values are changed, the
|
|
218
|
-
`render` cache becomes obsolete and causes the `refresh`
|
|
219
|
-
obsolete as well and re-executed. While being executed, the `refresh`
|
|
220
|
-
|
|
224
|
+
`render` cache becomes obsolete and causes the reaction function `refresh` to become
|
|
225
|
+
obsolete as well and re-executed. While being executed, the reaction function `refresh`
|
|
226
|
+
enqueues re-rendering request to React, which calls
|
|
221
227
|
`render` function causing it to renew its cached value.
|
|
222
228
|
|
|
223
|
-
In general case, all
|
|
229
|
+
In general case, all reactions and caches are automatically and
|
|
224
230
|
immediately marked as obsolete when changes are made in those observable
|
|
225
231
|
objects and other cached functions that were used during their execution.
|
|
226
232
|
And once marked, the functions are automatically executed again,
|
|
227
|
-
either immediately (for
|
|
228
|
-
(for
|
|
233
|
+
either immediately (for reaction functions) or on-demand
|
|
234
|
+
(for cache functions).
|
|
229
235
|
|
|
230
236
|
Reactronic takes full care of tracking dependencies between
|
|
231
|
-
all the observable objects and
|
|
237
|
+
all the observable objects and reaction/caches.
|
|
232
238
|
With Reactronic, you no longer need to create data change events
|
|
233
239
|
in one set of objects, subscribe to these events in other objects,
|
|
234
240
|
and manually maintain switching from the previous object version
|
|
@@ -238,20 +244,20 @@ to a new one.
|
|
|
238
244
|
|
|
239
245
|
There are multiple options to configure behavior of transactional reactivity.
|
|
240
246
|
|
|
241
|
-
**Order** options defines order of execution for
|
|
247
|
+
**Order** options defines order of execution for reactions:
|
|
242
248
|
|
|
243
249
|
- (TBD)
|
|
244
250
|
|
|
245
|
-
**Throttling** option defines how often
|
|
251
|
+
**Throttling** option defines how often reaction is executed in case
|
|
246
252
|
of recurring changes:
|
|
247
253
|
|
|
248
254
|
- `(ms)` - minimal delay in milliseconds between executions;
|
|
249
255
|
- `-1` - execute immediately once transaction is applied (synchronously);
|
|
250
256
|
- `0` - execute immediately via event loop (asynchronously with zero timeout);
|
|
251
|
-
- `>= Number.MAX_SAFE_INTEGER` - never execute (suspended
|
|
257
|
+
- `>= Number.MAX_SAFE_INTEGER` - never execute (suspended reaction).
|
|
252
258
|
|
|
253
|
-
**Reentrance** option defines how to handle reentrant calls of
|
|
254
|
-
and
|
|
259
|
+
**Reentrance** option defines how to handle reentrant calls of applying
|
|
260
|
+
and reaction functions:
|
|
255
261
|
|
|
256
262
|
- `preventWithError` - fail with error if there is an existing call in progress;
|
|
257
263
|
- `waitAndRestart` - wait for previous call to finish and then restart current one;
|
|
@@ -297,13 +303,14 @@ class ObservableObject { }
|
|
|
297
303
|
|
|
298
304
|
// Decorators & Operators
|
|
299
305
|
|
|
300
|
-
function
|
|
301
|
-
function
|
|
302
|
-
function
|
|
303
|
-
function
|
|
306
|
+
function observable(proto, prop) // field only
|
|
307
|
+
function unobservable(proto, prop) // field only
|
|
308
|
+
function apply(proto, prop, pd) // method only
|
|
309
|
+
function reaction(proto, prop, pd) // method only
|
|
310
|
+
function cache(proto, prop, pd) // method only
|
|
304
311
|
function options(value: Partial<MemberOptions>): F<any>
|
|
305
312
|
|
|
306
|
-
function
|
|
313
|
+
function nonreactive<T>(func: F<T>, ...args: any[]): T
|
|
307
314
|
function sensitive<T>(sensitivity: Sensitivity, func: F<T>, ...args: any[]): T
|
|
308
315
|
|
|
309
316
|
// SnapshotOptions, MemberOptions, Kind, Reentrance, Indicator, LoggingOptions, ProfilingOptions
|
|
@@ -331,9 +338,9 @@ type MemberOptions = {
|
|
|
331
338
|
|
|
332
339
|
enum Kind {
|
|
333
340
|
plain = 0,
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
341
|
+
transaction = 1,
|
|
342
|
+
reaction = 2,
|
|
343
|
+
cache = 3
|
|
337
344
|
}
|
|
338
345
|
|
|
339
346
|
enum Reentrance {
|
|
@@ -414,24 +421,24 @@ class Transaction implements Worker {
|
|
|
414
421
|
static isCanceled: boolean
|
|
415
422
|
}
|
|
416
423
|
|
|
417
|
-
//
|
|
424
|
+
// Operation
|
|
418
425
|
|
|
419
|
-
abstract class
|
|
426
|
+
abstract class Operation<T> {
|
|
420
427
|
readonly options: Options
|
|
421
428
|
readonly args: ReadonlyArray<any>
|
|
422
429
|
readonly value: T
|
|
423
430
|
readonly error: any
|
|
424
431
|
readonly stamp: number
|
|
425
|
-
readonly
|
|
432
|
+
readonly isReusable: boolean
|
|
426
433
|
|
|
427
434
|
configure(options: Partial<Options>): Options
|
|
428
435
|
markObsolete(): boolean
|
|
429
436
|
pullLastResult(args?: any[]): T | undefined
|
|
430
437
|
}
|
|
431
438
|
|
|
432
|
-
//
|
|
439
|
+
// ReactiveSystem
|
|
433
440
|
|
|
434
|
-
class
|
|
441
|
+
class ReactiveSystem {
|
|
435
442
|
static why(short: boolean = false): string
|
|
436
443
|
static getMethodCache<T>(method: F<T>): Cache<T>
|
|
437
444
|
static configureCurrentOperation(options: Partial<Options>): Options
|
|
@@ -8,7 +8,7 @@ var __metadata = (this && this.__metadata) || function (k, v) {
|
|
|
8
8
|
if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
|
|
9
9
|
};
|
|
10
10
|
import { ObservableObject } from "./core/Mvcc.js";
|
|
11
|
-
import {
|
|
11
|
+
import { apply } from "./ReactiveSystem.js";
|
|
12
12
|
export class Clock extends ObservableObject {
|
|
13
13
|
constructor(interval = 1000) {
|
|
14
14
|
super();
|
|
@@ -40,13 +40,13 @@ export class Clock extends ObservableObject {
|
|
|
40
40
|
}
|
|
41
41
|
}
|
|
42
42
|
__decorate([
|
|
43
|
-
|
|
43
|
+
apply,
|
|
44
44
|
__metadata("design:type", Function),
|
|
45
45
|
__metadata("design:paramtypes", [Boolean]),
|
|
46
46
|
__metadata("design:returntype", void 0)
|
|
47
47
|
], Clock.prototype, "pause", null);
|
|
48
48
|
__decorate([
|
|
49
|
-
|
|
49
|
+
apply,
|
|
50
50
|
__metadata("design:type", Function),
|
|
51
51
|
__metadata("design:paramtypes", []),
|
|
52
52
|
__metadata("design:returntype", void 0)
|
|
@@ -2,9 +2,9 @@ export { LoggingLevel } from "./Logging.js";
|
|
|
2
2
|
export var Kind;
|
|
3
3
|
(function (Kind) {
|
|
4
4
|
Kind[Kind["plain"] = 0] = "plain";
|
|
5
|
-
Kind[Kind["
|
|
6
|
-
Kind[Kind["
|
|
7
|
-
Kind[Kind["
|
|
5
|
+
Kind[Kind["apply"] = 1] = "apply";
|
|
6
|
+
Kind[Kind["reaction"] = 2] = "reaction";
|
|
7
|
+
Kind[Kind["cache"] = 3] = "cache";
|
|
8
8
|
})(Kind || (Kind = {}));
|
|
9
9
|
export var Reentrance;
|
|
10
10
|
(function (Reentrance) {
|
|
@@ -8,7 +8,7 @@ var __metadata = (this && this.__metadata) || function (k, v) {
|
|
|
8
8
|
if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
|
|
9
9
|
};
|
|
10
10
|
import { ObservableObject } from "./core/Mvcc.js";
|
|
11
|
-
import {
|
|
11
|
+
import { reaction } from "./ReactiveSystem.js";
|
|
12
12
|
export class Reaction extends ObservableObject {
|
|
13
13
|
constructor(action) {
|
|
14
14
|
super();
|
|
@@ -19,7 +19,7 @@ export class Reaction extends ObservableObject {
|
|
|
19
19
|
}
|
|
20
20
|
}
|
|
21
21
|
__decorate([
|
|
22
|
-
|
|
22
|
+
reaction,
|
|
23
23
|
__metadata("design:type", Function),
|
|
24
24
|
__metadata("design:paramtypes", []),
|
|
25
25
|
__metadata("design:returntype", Object)
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { F } from "./util/Utils.js";
|
|
2
|
-
import { Operation, MemberOptions, LoggingOptions, ProfilingOptions } from "./Options.js";
|
|
2
|
+
import { Operation, MemberOptions, LoggingOptions, ProfilingOptions, SnapshotOptions } from "./Options.js";
|
|
3
3
|
export declare class ReactiveSystem {
|
|
4
4
|
static why(brief?: boolean): string;
|
|
5
5
|
static getOperation<T>(method: F<T>): Operation<T>;
|
|
@@ -17,13 +17,14 @@ export declare class ReactiveSystem {
|
|
|
17
17
|
static getLoggingHint<T extends object>(obj: T, full?: boolean): string | undefined;
|
|
18
18
|
static setProfilingMode(isOn: boolean, options?: Partial<ProfilingOptions>): void;
|
|
19
19
|
}
|
|
20
|
-
export declare function
|
|
21
|
-
export declare function unobs<T>(func: F<T>, ...args: any[]): T;
|
|
20
|
+
export declare function nonreactive<T>(func: F<T>, ...args: any[]): T;
|
|
22
21
|
export declare function sensitive<T>(sensitivity: boolean, func: F<T>, ...args: any[]): T;
|
|
23
22
|
export declare function contextually<T>(p: Promise<T>): Promise<T>;
|
|
24
|
-
export declare function
|
|
25
|
-
export declare function
|
|
26
|
-
export declare function
|
|
27
|
-
export declare function
|
|
28
|
-
export declare function
|
|
23
|
+
export declare function apply<T>(func: F<T>, ...args: any[]): T;
|
|
24
|
+
export declare function apply<T>(options: SnapshotOptions, func: F<T>, ...args: any[]): T;
|
|
25
|
+
export declare function apply(proto: object, prop: PropertyKey, pd: PropertyDescriptor): any;
|
|
26
|
+
export declare function unobservable(proto: object, prop: PropertyKey): any;
|
|
27
|
+
export declare function observable(proto: object, prop: PropertyKey): any;
|
|
28
|
+
export declare function reaction(proto: object, prop: PropertyKey, pd: PropertyDescriptor): any;
|
|
29
|
+
export declare function cache(proto: object, prop: PropertyKey, pd: PropertyDescriptor): any;
|
|
29
30
|
export declare function options(value: Partial<MemberOptions>): F<any>;
|
|
@@ -22,10 +22,7 @@ export class ReactiveSystem {
|
|
|
22
22
|
static getLoggingHint(obj, full = false) { return ObjectHandle.getHint(obj, full); }
|
|
23
23
|
static setProfilingMode(isOn, options) { Mvcc.setProfilingMode(isOn, options); }
|
|
24
24
|
}
|
|
25
|
-
export function
|
|
26
|
-
return Transaction.run(null, action, ...args);
|
|
27
|
-
}
|
|
28
|
-
export function unobs(func, ...args) {
|
|
25
|
+
export function nonreactive(func, ...args) {
|
|
29
26
|
return OperationImpl.proceedWithinGivenLaunch(undefined, func, ...args);
|
|
30
27
|
}
|
|
31
28
|
export function sensitive(sensitivity, func, ...args) {
|
|
@@ -34,34 +31,48 @@ export function sensitive(sensitivity, func, ...args) {
|
|
|
34
31
|
export function contextually(p) {
|
|
35
32
|
throw new Error("not implemented yet");
|
|
36
33
|
}
|
|
37
|
-
export function
|
|
34
|
+
export function apply(p1, p2, p3) {
|
|
35
|
+
if (p1 instanceof Function) {
|
|
36
|
+
if (p2 !== undefined)
|
|
37
|
+
return Transaction.run(null, p1, ...p2);
|
|
38
|
+
else
|
|
39
|
+
return Transaction.run(null, p1);
|
|
40
|
+
}
|
|
41
|
+
else if (p2 instanceof Function) {
|
|
42
|
+
if (p3 !== undefined)
|
|
43
|
+
return Transaction.run(p1, p2, ...p3);
|
|
44
|
+
else
|
|
45
|
+
return Transaction.run(p1, p2);
|
|
46
|
+
}
|
|
47
|
+
else {
|
|
48
|
+
const opts = {
|
|
49
|
+
kind: Kind.apply,
|
|
50
|
+
isolation: Isolation.joinToCurrentTransaction,
|
|
51
|
+
};
|
|
52
|
+
return Mvcc.decorateOperation(true, apply, opts, p1, p2, p3);
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
export function unobservable(proto, prop) {
|
|
38
56
|
return Mvcc.decorateData(false, proto, prop);
|
|
39
57
|
}
|
|
40
|
-
export function
|
|
58
|
+
export function observable(proto, prop) {
|
|
41
59
|
return Mvcc.decorateData(true, proto, prop);
|
|
42
60
|
}
|
|
43
|
-
export function
|
|
44
|
-
const opts = {
|
|
45
|
-
kind: Kind.transactional,
|
|
46
|
-
isolation: Isolation.joinToCurrentTransaction,
|
|
47
|
-
};
|
|
48
|
-
return Mvcc.decorateOperation(true, transactional, opts, proto, prop, pd);
|
|
49
|
-
}
|
|
50
|
-
export function reactive(proto, prop, pd) {
|
|
61
|
+
export function reaction(proto, prop, pd) {
|
|
51
62
|
const opts = {
|
|
52
|
-
kind: Kind.
|
|
63
|
+
kind: Kind.reaction,
|
|
53
64
|
isolation: Isolation.joinAsNestedTransaction,
|
|
54
65
|
throttling: -1,
|
|
55
66
|
};
|
|
56
|
-
return Mvcc.decorateOperation(true,
|
|
67
|
+
return Mvcc.decorateOperation(true, reaction, opts, proto, prop, pd);
|
|
57
68
|
}
|
|
58
|
-
export function
|
|
69
|
+
export function cache(proto, prop, pd) {
|
|
59
70
|
const opts = {
|
|
60
|
-
kind: Kind.
|
|
71
|
+
kind: Kind.cache,
|
|
61
72
|
isolation: Isolation.joinToCurrentTransaction,
|
|
62
73
|
noSideEffects: true,
|
|
63
74
|
};
|
|
64
|
-
return Mvcc.decorateOperation(true,
|
|
75
|
+
return Mvcc.decorateOperation(true, cache, opts, proto, prop, pd);
|
|
65
76
|
}
|
|
66
77
|
export function options(value) {
|
|
67
78
|
return Mvcc.decorateOperationParametrized(options, value);
|
package/build/dist/source/Ref.js
CHANGED
|
@@ -1,5 +1,4 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import { unobs } from "./ReactiveSystem.js";
|
|
1
|
+
import { apply, nonreactive } from "./ReactiveSystem.js";
|
|
3
2
|
export function refs(owner) {
|
|
4
3
|
return new Proxy(owner, RefGettingProxy);
|
|
5
4
|
}
|
|
@@ -29,7 +28,7 @@ export class Ref {
|
|
|
29
28
|
this.owner[this.name][this.index] = value;
|
|
30
29
|
}
|
|
31
30
|
unobs() {
|
|
32
|
-
return
|
|
31
|
+
return nonreactive(() => this.variable);
|
|
33
32
|
}
|
|
34
33
|
observe() {
|
|
35
34
|
return this.variable;
|
|
@@ -53,7 +52,7 @@ export class ToggleRef extends Ref {
|
|
|
53
52
|
toggle() {
|
|
54
53
|
const o = this.owner;
|
|
55
54
|
const p = this.name;
|
|
56
|
-
|
|
55
|
+
apply({ hint: `toggle ${o.constructor.name}.${p}` }, () => {
|
|
57
56
|
const v = o[p];
|
|
58
57
|
const isOn = v === this.valueOn || (v instanceof Ref && this.valueOn instanceof Ref &&
|
|
59
58
|
Ref.sameRefs(v, this.valueOn));
|
|
@@ -16,7 +16,7 @@ export { Changeset } from "./core/Changeset.js";
|
|
|
16
16
|
export { Transaction } from "./core/Transaction.js";
|
|
17
17
|
export { Indicator } from "./core/Indicator.js";
|
|
18
18
|
export { Journal } from "./core/Journal.js";
|
|
19
|
-
export { ReactiveSystem,
|
|
19
|
+
export { ReactiveSystem, observable, unobservable, apply, reaction, cache, nonreactive, sensitive, contextually, options } from "./ReactiveSystem.js";
|
|
20
20
|
export { Reaction } from "./Reaction.js";
|
|
21
21
|
export { ReactiveNode, Mode, Priority, BaseDriver, ReactiveNodeVariable } from "./core/ReactiveNode.js";
|
|
22
22
|
export type { Script, ScriptAsync, Handler, ReactiveNodeDecl, ReactiveNodeDriver, ReactiveNodeContext } from "./core/ReactiveNode.js";
|
package/build/dist/source/api.js
CHANGED
|
@@ -12,7 +12,7 @@ export { Changeset } from "./core/Changeset.js";
|
|
|
12
12
|
export { Transaction } from "./core/Transaction.js";
|
|
13
13
|
export { Indicator } from "./core/Indicator.js";
|
|
14
14
|
export { Journal } from "./core/Journal.js";
|
|
15
|
-
export { ReactiveSystem,
|
|
15
|
+
export { ReactiveSystem, observable, unobservable, apply, reaction, cache, nonreactive, sensitive, contextually, options } from "./ReactiveSystem.js";
|
|
16
16
|
export { Reaction } from "./Reaction.js";
|
|
17
17
|
export { ReactiveNode, Mode, Priority, BaseDriver, ReactiveNodeVariable } from "./core/ReactiveNode.js";
|
|
18
18
|
export { Clock } from "./Clock.js";
|
|
@@ -8,7 +8,7 @@ export declare class OperationImpl implements Operation<any> {
|
|
|
8
8
|
readonly fieldKey: FieldKey;
|
|
9
9
|
configure(options: Partial<MemberOptions>): MemberOptions;
|
|
10
10
|
get options(): MemberOptions;
|
|
11
|
-
get
|
|
11
|
+
get nonreactive(): any;
|
|
12
12
|
get args(): ReadonlyArray<any>;
|
|
13
13
|
get result(): any;
|
|
14
14
|
get error(): boolean;
|
|
@@ -12,7 +12,7 @@ const EMPTY_HANDLE = new ObjectHandle(undefined, undefined, Mvcc.observable, EMP
|
|
|
12
12
|
export class OperationImpl {
|
|
13
13
|
configure(options) { return OperationImpl.configureImpl(this, options); }
|
|
14
14
|
get options() { return this.peek(undefined).launch.options; }
|
|
15
|
-
get
|
|
15
|
+
get nonreactive() { return this.peek(undefined).launch.content; }
|
|
16
16
|
get args() { return this.use().launch.args; }
|
|
17
17
|
get result() { return this.reuseOrRelaunch(true, undefined).content; }
|
|
18
18
|
get error() { return this.use().launch.error; }
|
|
@@ -99,7 +99,7 @@ export class OperationImpl {
|
|
|
99
99
|
const ov = ctx.lookupObjectVersion(this.ownerHandle, this.fieldKey, false);
|
|
100
100
|
const launch = this.acquireFromObjectVersion(ov, args);
|
|
101
101
|
const applied = this.ownerHandle.applied.data[this.fieldKey];
|
|
102
|
-
const isReusable = launch.options.kind !== Kind.
|
|
102
|
+
const isReusable = launch.options.kind !== Kind.apply && launch.cause !== BOOT_CAUSE &&
|
|
103
103
|
(ctx === launch.changeset || ctx.timestamp < launch.obsoleteSince || applied.obsoleteDueTo === undefined) &&
|
|
104
104
|
(!launch.options.triggeringArgs || args === undefined ||
|
|
105
105
|
launch.args.length === args.length && launch.args.every((t, i) => t === args[i])) || ov.disposed;
|
|
@@ -175,7 +175,7 @@ export class OperationImpl {
|
|
|
175
175
|
}
|
|
176
176
|
else {
|
|
177
177
|
ror = this.peek(argsx);
|
|
178
|
-
if (ror.launch.options.kind === Kind.
|
|
178
|
+
if (ror.launch.options.kind === Kind.apply || !ror.isReusable) {
|
|
179
179
|
ror = this.edit();
|
|
180
180
|
if (Log.isOn && Log.opt.operation)
|
|
181
181
|
Log.write("║", " o", `${ror.launch.why()}`);
|
|
@@ -251,7 +251,7 @@ class Launch extends FieldVersion {
|
|
|
251
251
|
let cause;
|
|
252
252
|
if (this.cause)
|
|
253
253
|
cause = ` ◀◀ ${this.cause}`;
|
|
254
|
-
else if (this.operation.options.kind === Kind.
|
|
254
|
+
else if (this.operation.options.kind === Kind.apply)
|
|
255
255
|
cause = " ◀◀ operation";
|
|
256
256
|
else
|
|
257
257
|
cause = ` ◀◀ T${this.changeset.id}[${this.changeset.hint}]`;
|
|
@@ -294,7 +294,7 @@ class Launch extends FieldVersion {
|
|
|
294
294
|
changeset.id === this.lastEditorChangesetId;
|
|
295
295
|
if (!skip) {
|
|
296
296
|
const why = `${Dump.snapshot2(h, changeset, fk, observable)} ◀◀ ${outer}`;
|
|
297
|
-
const isReactive = this.options.kind === Kind.
|
|
297
|
+
const isReactive = this.options.kind === Kind.reaction;
|
|
298
298
|
this.obsoleteDueTo = why;
|
|
299
299
|
this.obsoleteSince = since;
|
|
300
300
|
if (Log.isOn && (Log.opt.obsolete || ((_a = this.options.logging) === null || _a === void 0 ? void 0 : _a.obsolete)))
|
|
@@ -326,14 +326,14 @@ class Launch extends FieldVersion {
|
|
|
326
326
|
const launch = this.operation.reuseOrRelaunch(false, undefined);
|
|
327
327
|
if (launch.result instanceof Promise)
|
|
328
328
|
launch.result.catch(error => {
|
|
329
|
-
if (launch.options.kind === Kind.
|
|
329
|
+
if (launch.options.kind === Kind.reaction)
|
|
330
330
|
misuse(`reactive function ${launch.hint()} failed and will not run anymore: ${error}`, error);
|
|
331
331
|
});
|
|
332
332
|
}
|
|
333
333
|
catch (e) {
|
|
334
334
|
if (!nothrow)
|
|
335
335
|
throw e;
|
|
336
|
-
else if (this.options.kind === Kind.
|
|
336
|
+
else if (this.options.kind === Kind.reaction)
|
|
337
337
|
misuse(`reactive ${this.hint()} failed and will not run anymore: ${e}`, e);
|
|
338
338
|
}
|
|
339
339
|
}
|
|
@@ -346,7 +346,7 @@ class Launch extends FieldVersion {
|
|
|
346
346
|
}
|
|
347
347
|
}
|
|
348
348
|
isNotUpToDate() {
|
|
349
|
-
return !this.error && (this.options.kind === Kind.
|
|
349
|
+
return !this.error && (this.options.kind === Kind.apply ||
|
|
350
350
|
!this.successor || this.successor.transaction.isCanceled);
|
|
351
351
|
}
|
|
352
352
|
reenterOver(head) {
|
|
@@ -469,9 +469,9 @@ class Launch extends FieldVersion {
|
|
|
469
469
|
x.relaunchIfNotUpToDate(true, true);
|
|
470
470
|
}
|
|
471
471
|
static markUsed(observable, ov, fk, h, kind, weak) {
|
|
472
|
-
if (kind !== Kind.
|
|
472
|
+
if (kind !== Kind.apply) {
|
|
473
473
|
const launch = Launch.current;
|
|
474
|
-
if (launch && launch.options.kind !== Kind.
|
|
474
|
+
if (launch && launch.options.kind !== Kind.apply &&
|
|
475
475
|
launch.transaction === Transaction.current && fk !== Meta.Handle) {
|
|
476
476
|
const ctx = Changeset.current();
|
|
477
477
|
if (ctx !== ov.changeset)
|
|
@@ -652,11 +652,11 @@ class Launch extends FieldVersion {
|
|
|
652
652
|
const rx = launch ? launch.operation : new OperationImpl(EMPTY_HANDLE, fk);
|
|
653
653
|
const opts = launch ? launch.options : OptionsImpl.INITIAL;
|
|
654
654
|
initial[fk] = launch = new Launch(Transaction.current, rx, EMPTY_OBJECT_VERSION.changeset, new OptionsImpl(getter, setter, opts, options, implicit), false);
|
|
655
|
-
if (launch.options.kind === Kind.
|
|
655
|
+
if (launch.options.kind === Kind.reaction && launch.options.throttling < Number.MAX_SAFE_INTEGER) {
|
|
656
656
|
const reactive = Meta.acquire(proto, Meta.Reactive);
|
|
657
657
|
reactive[fk] = launch;
|
|
658
658
|
}
|
|
659
|
-
else if (launch.options.kind === Kind.
|
|
659
|
+
else if (launch.options.kind === Kind.reaction && launch.options.throttling >= Number.MAX_SAFE_INTEGER) {
|
|
660
660
|
const reactive = Meta.getFrom(proto, Meta.Reactive);
|
|
661
661
|
delete reactive[fk];
|
|
662
662
|
}
|
|
@@ -22,7 +22,7 @@ import { emitLetters, getCallerInfo, proceedSyncOrAsync } from "../util/Utils.js
|
|
|
22
22
|
import { Isolation, Reentrance } from "../Options.js";
|
|
23
23
|
import { ObservableObject } from "../core/Mvcc.js";
|
|
24
24
|
import { Transaction } from "../core/Transaction.js";
|
|
25
|
-
import { ReactiveSystem, options,
|
|
25
|
+
import { ReactiveSystem, options, unobservable, reaction, nonreactive, apply } from "../ReactiveSystem.js";
|
|
26
26
|
export var Mode;
|
|
27
27
|
(function (Mode) {
|
|
28
28
|
Mode[Mode["default"] = 0] = "default";
|
|
@@ -259,11 +259,11 @@ class ReactiveNodeContextImpl extends ObservableObject {
|
|
|
259
259
|
}
|
|
260
260
|
}
|
|
261
261
|
__decorate([
|
|
262
|
-
|
|
262
|
+
unobservable,
|
|
263
263
|
__metadata("design:type", Object)
|
|
264
264
|
], ReactiveNodeContextImpl.prototype, "next", void 0);
|
|
265
265
|
__decorate([
|
|
266
|
-
|
|
266
|
+
unobservable,
|
|
267
267
|
__metadata("design:type", ReactiveNodeVariable)
|
|
268
268
|
], ReactiveNodeContextImpl.prototype, "variable", void 0);
|
|
269
269
|
class ReactiveNodeImpl extends ReactiveNode {
|
|
@@ -333,13 +333,13 @@ class ReactiveNodeImpl extends ReactiveNode {
|
|
|
333
333
|
static setNodeVariableValue(variable, value) {
|
|
334
334
|
const node = ReactiveNodeImpl.ownSlot.instance;
|
|
335
335
|
const owner = node.owner;
|
|
336
|
-
const hostCtx =
|
|
336
|
+
const hostCtx = nonreactive(() => { var _a; return (_a = owner.context) === null || _a === void 0 ? void 0 : _a.value; });
|
|
337
337
|
if (value && value !== hostCtx) {
|
|
338
338
|
if (hostCtx)
|
|
339
339
|
node.outer = owner;
|
|
340
340
|
else
|
|
341
341
|
node.outer = owner.outer;
|
|
342
|
-
|
|
342
|
+
apply({ isolation: Isolation.joinAsNestedTransaction }, () => {
|
|
343
343
|
const ctx = node.context;
|
|
344
344
|
if (ctx) {
|
|
345
345
|
ctx.variable = variable;
|
|
@@ -359,7 +359,7 @@ ReactiveNodeImpl.logging = undefined;
|
|
|
359
359
|
ReactiveNodeImpl.grandNodeCount = 0;
|
|
360
360
|
ReactiveNodeImpl.disposableNodeCount = 0;
|
|
361
361
|
__decorate([
|
|
362
|
-
|
|
362
|
+
reaction,
|
|
363
363
|
options({
|
|
364
364
|
reentrance: Reentrance.cancelAndWaitPrevious,
|
|
365
365
|
allowObsoleteToFinish: true,
|
|
@@ -484,7 +484,7 @@ function triggerUpdateViaSlot(slot) {
|
|
|
484
484
|
});
|
|
485
485
|
});
|
|
486
486
|
}
|
|
487
|
-
|
|
487
|
+
nonreactive(node.update, node.declaration.triggers);
|
|
488
488
|
}
|
|
489
489
|
else
|
|
490
490
|
updateNow(slot);
|
|
@@ -493,7 +493,7 @@ function triggerUpdateViaSlot(slot) {
|
|
|
493
493
|
function mountOrRemountIfNecessary(node) {
|
|
494
494
|
const driver = node.driver;
|
|
495
495
|
if (node.stamp === Number.MAX_SAFE_INTEGER) {
|
|
496
|
-
|
|
496
|
+
nonreactive(() => {
|
|
497
497
|
node.stamp = Number.MAX_SAFE_INTEGER - 1;
|
|
498
498
|
driver.runPreparation(node);
|
|
499
499
|
if (!node.has(Mode.manualMount)) {
|
|
@@ -504,7 +504,7 @@ function mountOrRemountIfNecessary(node) {
|
|
|
504
504
|
});
|
|
505
505
|
}
|
|
506
506
|
else if (node.isMoved && !node.has(Mode.manualMount) && node.host !== node)
|
|
507
|
-
|
|
507
|
+
nonreactive(() => driver.runMount(node));
|
|
508
508
|
}
|
|
509
509
|
function updateNow(slot) {
|
|
510
510
|
const node = slot.instance;
|
|
@@ -537,7 +537,7 @@ function triggerFinalization(slot, isLeader, individual) {
|
|
|
537
537
|
if (individual && node.key !== node.declaration.key && !driver.isPartition)
|
|
538
538
|
console.log(`WARNING: it is recommended to assign explicit key for conditional element in order to avoid unexpected side effects: ${node.key}`);
|
|
539
539
|
node.stamp = ~node.stamp;
|
|
540
|
-
const childrenAreLeaders =
|
|
540
|
+
const childrenAreLeaders = nonreactive(() => driver.runFinalization(node, isLeader));
|
|
541
541
|
if (node.has(Mode.autonomous)) {
|
|
542
542
|
slot.aux = undefined;
|
|
543
543
|
const last = gLastToDispose;
|
|
@@ -546,7 +546,7 @@ function triggerFinalization(slot, isLeader, individual) {
|
|
|
546
546
|
else
|
|
547
547
|
gFirstToDispose = gLastToDispose = slot;
|
|
548
548
|
if (gFirstToDispose === slot)
|
|
549
|
-
|
|
549
|
+
apply({ isolation: Isolation.disjoinForInternalDisposal, hint: `runDisposalLoop(initiator=${slot.instance.key})` }, () => {
|
|
550
550
|
void runDisposalLoop().then(NOP, error => console.log(error));
|
|
551
551
|
});
|
|
552
552
|
}
|
|
@@ -6,13 +6,6 @@ export type MergeListReader<T> = {
|
|
|
6
6
|
readonly removedCount: number;
|
|
7
7
|
readonly isMergeInProgress: boolean;
|
|
8
8
|
lookup(key: string): MergedItem<T> | undefined;
|
|
9
|
-
tryMergeAsExisting(key: string): MergedItem<T> | undefined;
|
|
10
|
-
mergeAsAdded(instance: T): MergedItem<T>;
|
|
11
|
-
mergeAsRemoved(item: MergedItem<T>): void;
|
|
12
|
-
move(item: MergedItem<T>, after: MergedItem<T>): void;
|
|
13
|
-
beginMerge(): void;
|
|
14
|
-
endMerge(error?: unknown): void;
|
|
15
|
-
resetAddedAndRemovedLists(): void;
|
|
16
9
|
firstMergedItem(): MergedItem<T> | undefined;
|
|
17
10
|
lastMergedItem(): MergedItem<T> | undefined;
|
|
18
11
|
items(onlyAfter?: MergedItem<T>): Generator<MergedItem<T>>;
|