reactronic 0.92.25005 → 0.92.25007
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 +151 -125
- package/build/dist/source/Options.d.ts +3 -3
- package/build/dist/source/Options.js +3 -3
- package/build/dist/source/Reaction.d.ts +1 -1
- package/build/dist/source/Reaction.js +4 -4
- package/build/dist/source/ReactiveSystem.d.ts +5 -7
- package/build/dist/source/ReactiveSystem.js +24 -42
- package/build/dist/source/Ref.js +2 -2
- package/build/dist/source/api.d.ts +3 -2
- package/build/dist/source/api.js +3 -2
- package/build/dist/source/core/Operation.js +11 -11
- package/build/dist/source/core/ReactiveNode.js +4 -4
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -13,23 +13,28 @@ Reactronic is an experimental JavaScript library that provides
|
|
|
13
13
|
[transactional reactive](https://blog.nezaboodka.com/post/2019/593-modern-database-should-natively-support-transactionally-reactive-programming)
|
|
14
14
|
state management in a Web application.
|
|
15
15
|
|
|
16
|
-
Transactional reactivity means that state changes are
|
|
17
|
-
isolated data snapshot and then, once
|
|
18
|
-
**consistently propagated** to
|
|
19
|
-
(re)rendering. All
|
|
16
|
+
Transactional reactivity means that state changes are
|
|
17
|
+
being made in an isolated data snapshot and then, once
|
|
18
|
+
atomically applied, are **consistently propagated** to
|
|
19
|
+
corresponding visual components for (re)rendering. All
|
|
20
|
+
that is done in automatic, seamless, and fine-grained
|
|
20
21
|
way. Reactronic **takes full care of tracking dependencies**
|
|
21
|
-
between visual components (observers) and state
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
- **
|
|
28
|
-
|
|
29
|
-
- **
|
|
30
|
-
|
|
31
|
-
- **
|
|
32
|
-
|
|
22
|
+
between visual components (observers) and state
|
|
23
|
+
(observable objects).
|
|
24
|
+
|
|
25
|
+
Transactional reactivity is based on four fundamental
|
|
26
|
+
concepts:
|
|
27
|
+
|
|
28
|
+
- **Observable Objects** - a set of objects that store
|
|
29
|
+
data of an application (state);
|
|
30
|
+
- **Atomic Action** - a function that makes changes in
|
|
31
|
+
observable objects in atomic way ("all or nothing");
|
|
32
|
+
- **Reactive Process** - recurrent and automatic
|
|
33
|
+
(re-)execution of a function in response to changes
|
|
34
|
+
made by atomic actions;
|
|
35
|
+
- **Cached Result** - result value of a function that
|
|
36
|
+
is remembered and, if the becomes obsolete, causes
|
|
37
|
+
its function to re-execute on-demand.
|
|
33
38
|
|
|
34
39
|
Demo application built with Reactronic: https://nevod.io/#/playground.
|
|
35
40
|
Source code of the demo: https://gitlab.com/nezaboodka/nevod.web.public/-/blob/master/README.md.
|
|
@@ -51,7 +56,7 @@ class Demo extends ObservableObject {
|
|
|
51
56
|
this.email = email
|
|
52
57
|
}
|
|
53
58
|
|
|
54
|
-
@
|
|
59
|
+
@reactiveProcess
|
|
55
60
|
printContact(): void {
|
|
56
61
|
// depends on `name` and `email` and reacts to their changes
|
|
57
62
|
if (this.email.indexOf('@') >= 0)
|
|
@@ -63,25 +68,26 @@ class Demo extends ObservableObject {
|
|
|
63
68
|
|
|
64
69
|
In the example above, `Demo` is an observable object,
|
|
65
70
|
meaning that access to its fields are seamlessly tracked
|
|
66
|
-
to determine dependent
|
|
67
|
-
`printContact` reads `name`
|
|
68
|
-
depends on them. It is executed
|
|
69
|
-
response to changes of these fields
|
|
70
|
-
action `saveContact`.
|
|
71
|
+
to determine dependent reactive processes and cached
|
|
72
|
+
results. Reactive process `printContact` reads `name`
|
|
73
|
+
and `email` fields, thus depends on them. It is executed
|
|
74
|
+
automatically in response to changes of these fields
|
|
75
|
+
made by the atomic action `saveContact`.
|
|
71
76
|
|
|
72
|
-
Here is an example of a cached
|
|
77
|
+
Here is an example of a cached result that is
|
|
78
|
+
(re-)computed on-demand:
|
|
73
79
|
|
|
74
80
|
``` typescript
|
|
75
81
|
class Demo extends ObservableObject {
|
|
76
82
|
name: string = 'Nezaboodka Software'
|
|
77
83
|
email: string = 'contact@nezaboodka.com'
|
|
78
84
|
|
|
79
|
-
@
|
|
85
|
+
@cachedResult
|
|
80
86
|
get contact(): string {
|
|
81
87
|
return this.name + ' <' + this.email + '>'
|
|
82
88
|
}
|
|
83
89
|
|
|
84
|
-
@
|
|
90
|
+
@reactiveProcess
|
|
85
91
|
printContact(): void {
|
|
86
92
|
if (this.contact !== '')
|
|
87
93
|
Console.log(this.contact)
|
|
@@ -89,19 +95,22 @@ class Demo extends ObservableObject {
|
|
|
89
95
|
}
|
|
90
96
|
```
|
|
91
97
|
|
|
92
|
-
In the example above, the
|
|
93
|
-
source fields `name` and `email
|
|
94
|
-
the result is cached and is reused until
|
|
95
|
-
and `email` are changed. Once
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
98
|
+
In the example above, the result of `contact` getter is
|
|
99
|
+
computed from source fields `name` and `email`. Once
|
|
100
|
+
computed, the result is cached and is reused until
|
|
101
|
+
source fields `name` and `email` are changed. Once
|
|
102
|
+
source fields changed, `contact` result becomes obsolete,
|
|
103
|
+
thus causing execution of depending reactive process
|
|
104
|
+
`printContact`. When function of reactive process
|
|
105
|
+
`printContact` runs it reads `contact` and causes its
|
|
106
|
+
re-computation.
|
|
99
107
|
|
|
100
108
|
## Observable Objects
|
|
101
109
|
|
|
102
|
-
Observable objects store data of an
|
|
103
|
-
|
|
104
|
-
both on reads and
|
|
110
|
+
Observable objects are aimed to store data of an
|
|
111
|
+
application. All such objects are transparently hooked
|
|
112
|
+
to track access to their properties, both on reads and
|
|
113
|
+
writes.
|
|
105
114
|
|
|
106
115
|
``` typescript
|
|
107
116
|
class MyModel extends ObservableObject {
|
|
@@ -111,18 +120,18 @@ class MyModel extends ObservableObject {
|
|
|
111
120
|
}
|
|
112
121
|
```
|
|
113
122
|
|
|
114
|
-
In the example above, the class `MyModel` is based on
|
|
115
|
-
`ObservableObject` class and all its
|
|
116
|
-
and `timestamp` are hooked.
|
|
123
|
+
In the example above, the class `MyModel` is based on
|
|
124
|
+
Reactronic's `ObservableObject` class and all its
|
|
125
|
+
properties `url`, `content`, and `timestamp` are hooked.
|
|
117
126
|
|
|
118
127
|
## Atomic Action
|
|
119
128
|
|
|
120
129
|
Atomic action makes changes in observable objects
|
|
121
|
-
in
|
|
122
|
-
of dependent
|
|
123
|
-
|
|
124
|
-
hooks to provide transparent atomicity
|
|
125
|
-
context switching and isolation).
|
|
130
|
+
in atomic (transactional) way, thus provoking execution
|
|
131
|
+
of dependent reactive processes and recalculation of
|
|
132
|
+
dependent cached results. Atomic action function is
|
|
133
|
+
instrumented with hooks to provide transparent atomicity
|
|
134
|
+
(by implicit context switching and isolation).
|
|
126
135
|
|
|
127
136
|
``` typescript
|
|
128
137
|
class MyModel extends ObservableObject {
|
|
@@ -137,48 +146,53 @@ class MyModel extends ObservableObject {
|
|
|
137
146
|
```
|
|
138
147
|
|
|
139
148
|
In the example above, the atomic action `load` makes
|
|
140
|
-
changes to `url`, `content` and `timestamp` properties.
|
|
141
|
-
atomic action is running, the changes are visible
|
|
142
|
-
action itself. The new values become
|
|
143
|
-
of the action only upon its
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
of
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
149
|
+
changes to `url`, `content` and `timestamp` properties.
|
|
150
|
+
While atomic action is running, the changes are visible
|
|
151
|
+
only inside the action itself. The new values become
|
|
152
|
+
atomically visible outside of the action only upon its
|
|
153
|
+
completion.
|
|
154
|
+
|
|
155
|
+
Atomicity is achieved by making changes in an isolated
|
|
156
|
+
data snapshot that is not visible outside of the running
|
|
157
|
+
action until it is fully finished and applied. Multiple
|
|
158
|
+
objects and their properties can be changed with full
|
|
159
|
+
respect to the all-or-nothing principle. To do so,
|
|
160
|
+
separate data snapshot is automatically maintained for
|
|
161
|
+
each atomic action. That is a logical snapshot that does
|
|
162
|
+
not create a full copy of all the data.
|
|
163
|
+
|
|
164
|
+
Compensating rollback operations are not needed in case
|
|
165
|
+
of the atomic action failure, because all the changes
|
|
166
|
+
made by the atomic action in its logical snapshot are
|
|
167
|
+
simply discarded. In case the atomic action is
|
|
168
|
+
successfully applied, affected cached results are marked
|
|
169
|
+
as obsolete and corresponding caching functions are
|
|
170
|
+
re-executed in a proper order (but only when all the
|
|
171
|
+
data changes are fully applied).
|
|
172
|
+
|
|
173
|
+
Asynchronous operations (promises) are supported out of
|
|
174
|
+
the box during atomic action execution. Atomic action
|
|
175
|
+
may consist of a set of asynchronous calls prolonging
|
|
176
|
+
the action until completion of all of them. An
|
|
177
|
+
asynchronous call may spawn other asynchronous calls,
|
|
178
|
+
which prolong atomic atomic execution until the whole
|
|
179
|
+
chain of asynchronous operations is fully completed.
|
|
180
|
+
|
|
181
|
+
## Reactive Process & Cached Result
|
|
182
|
+
|
|
183
|
+
Reactive process function is automatically and
|
|
184
|
+
immediately called in response to changes in observable
|
|
185
|
+
objects made by atomic actions. Function of cached result
|
|
186
|
+
is called on-demand to renew the result if it was marked
|
|
187
|
+
as obsolete due to changes made by an atomic action.
|
|
188
|
+
Functions of reactive processes and cached results are
|
|
189
|
+
instrumented with hooks to seamlessly subscribe to those
|
|
190
|
+
observable objects and other cached results
|
|
191
|
+
(dependencies), which are used during their execution.
|
|
178
192
|
|
|
179
193
|
``` tsx
|
|
180
194
|
class MyView extends Component<{model: MyModel}> {
|
|
181
|
-
@
|
|
195
|
+
@cachedResult
|
|
182
196
|
render(): React.JSX.Element {
|
|
183
197
|
return (
|
|
184
198
|
<div>
|
|
@@ -192,72 +206,84 @@ class MyView extends Component<{model: MyModel}> {
|
|
|
192
206
|
|
|
193
207
|
``` tsx
|
|
194
208
|
class Component<P> extends React.Component<P> {
|
|
195
|
-
@
|
|
209
|
+
@cachedResult
|
|
196
210
|
render(): React.JSX.Element {
|
|
197
211
|
throw new Error('render method is undefined')
|
|
198
212
|
}
|
|
199
213
|
|
|
200
|
-
@
|
|
214
|
+
@reactiveProcess // called in response to changes
|
|
201
215
|
ensureUpToDate(): void {
|
|
202
|
-
if (this.shouldComponentUpdate())
|
|
203
|
-
|
|
204
|
-
|
|
216
|
+
if (this.shouldComponentUpdate()) {
|
|
217
|
+
// Ask React to re-render
|
|
218
|
+
Transaction.outside(() => this.setState({}))
|
|
219
|
+
}
|
|
220
|
+
} // EnsureUpToDate is subscribed to render
|
|
205
221
|
|
|
206
222
|
shouldComponentUpdate(): boolean {
|
|
207
|
-
|
|
223
|
+
const r = ReactiveSystem.getController(this.render)
|
|
224
|
+
return !r.isUpToDate
|
|
208
225
|
}
|
|
209
226
|
|
|
210
227
|
componentDidMount(): void {
|
|
211
|
-
|
|
228
|
+
// Run to subscribe for the first time
|
|
229
|
+
this.ensureUpToDate()
|
|
212
230
|
}
|
|
213
231
|
|
|
214
232
|
componentWillUnmount(): void {
|
|
215
|
-
atomicAction(
|
|
233
|
+
atomicAction(ReactiveSystem.dispose, this)
|
|
216
234
|
}
|
|
217
235
|
}
|
|
218
236
|
```
|
|
219
237
|
|
|
220
|
-
In the example above,
|
|
221
|
-
to the
|
|
222
|
-
|
|
223
|
-
`
|
|
224
|
-
`
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
`
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
238
|
+
In the example above, reactive process `refresh` is
|
|
239
|
+
transparently subscribed to the cached result of
|
|
240
|
+
function `render`. In turn, cached result of function
|
|
241
|
+
`render` is subscribed to the properties `url` and
|
|
242
|
+
`content` of a corresponding `MyModel` object. Once
|
|
243
|
+
`url` or `content` values are changed, the `render`
|
|
244
|
+
cached result becomes obsolete and causes the reactive
|
|
245
|
+
process `refresh` to become obsolete and re-executed.
|
|
246
|
+
While being executed, the reactive process function
|
|
247
|
+
`refresh` enqueues re-rendering request to React, which
|
|
248
|
+
calls `render` function causing it to renew its cached
|
|
249
|
+
value.
|
|
250
|
+
|
|
251
|
+
In general case, all reactive processes and cached
|
|
252
|
+
results are automatically and immediately marked as
|
|
253
|
+
obsolete when changes are made in those observable
|
|
254
|
+
objects and other cached results that were used during
|
|
255
|
+
their execution. And once marked, the functions are
|
|
256
|
+
automatically executed again, either immediately (for
|
|
257
|
+
reactive processes) or on-demand (for cached results).
|
|
258
|
+
|
|
259
|
+
Reactronic takes full care of tracking dependencies
|
|
260
|
+
between all the observable objects and reactive processes
|
|
261
|
+
or cached results. With Reactronic, you no longer need
|
|
262
|
+
to create data change events in one set of objects,
|
|
263
|
+
subscribe to these events in other objects, and manually
|
|
264
|
+
maintain switching from the previous object version to a
|
|
265
|
+
new one.
|
|
242
266
|
|
|
243
267
|
## Behavior Options
|
|
244
268
|
|
|
245
|
-
There are multiple options to configure behavior of
|
|
269
|
+
There are multiple options to configure behavior of
|
|
270
|
+
transactional reactivity.
|
|
246
271
|
|
|
247
|
-
**Order** options defines order of execution for
|
|
272
|
+
**Order** options defines order of execution for
|
|
273
|
+
reactive processes:
|
|
248
274
|
|
|
249
275
|
- (TBD)
|
|
250
276
|
|
|
251
|
-
**Throttling** option defines how often
|
|
252
|
-
of recurring changes:
|
|
277
|
+
**Throttling** option defines how often reactive process
|
|
278
|
+
is executed in case of recurring changes:
|
|
253
279
|
|
|
254
280
|
- `(ms)` - minimal delay in milliseconds between executions;
|
|
255
|
-
- `-1` - execute immediately once
|
|
281
|
+
- `-1` - execute immediately once atomic action is applied (synchronously);
|
|
256
282
|
- `0` - execute immediately via event loop (asynchronously with zero timeout);
|
|
257
283
|
- `>= Number.MAX_SAFE_INTEGER` - never execute (suspended reaction).
|
|
258
284
|
|
|
259
285
|
**Reentrance** option defines how to handle reentrant calls of atomic
|
|
260
|
-
actions and
|
|
286
|
+
actions and reactive processes:
|
|
261
287
|
|
|
262
288
|
- `preventWithError` - fail with error if there is an existing call in progress;
|
|
263
289
|
- `waitAndRestart` - wait for previous call to finish and then restart current one;
|
|
@@ -267,8 +293,8 @@ actions and reactions:
|
|
|
267
293
|
|
|
268
294
|
**Indicator** is an object that maintains status of running functions,
|
|
269
295
|
which it is attached to. A single indicator object can be shared between
|
|
270
|
-
multiple
|
|
271
|
-
consolidated status for all of them (busy, workers, etc).
|
|
296
|
+
multiple atomic actions, reactive processes, and cached results, thus
|
|
297
|
+
maintaining consolidated status for all of them (busy, workers, etc).
|
|
272
298
|
|
|
273
299
|
## Notes
|
|
274
300
|
|
|
@@ -306,8 +332,8 @@ class ObservableObject { }
|
|
|
306
332
|
function observable(proto, prop) // field only
|
|
307
333
|
function unobservable(proto, prop) // field only
|
|
308
334
|
function atomicAction(proto, prop, pd) // method only
|
|
309
|
-
function
|
|
310
|
-
function
|
|
335
|
+
function reactiveProcess(proto, prop, pd) // method only
|
|
336
|
+
function cachedResult(proto, prop, pd) // method only
|
|
311
337
|
function options(value: Partial<MemberOptions>): F<any>
|
|
312
338
|
|
|
313
339
|
function nonreactive<T>(func: F<T>, ...args: any[]): T
|
|
@@ -338,9 +364,9 @@ type MemberOptions = {
|
|
|
338
364
|
|
|
339
365
|
enum Kind {
|
|
340
366
|
plain = 0,
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
367
|
+
atomicAction = 1,
|
|
368
|
+
reactiveProcess = 2,
|
|
369
|
+
cachedResult = 3
|
|
344
370
|
}
|
|
345
371
|
|
|
346
372
|
enum Reentrance {
|
|
@@ -25,9 +25,9 @@ export type MemberOptions = {
|
|
|
25
25
|
};
|
|
26
26
|
export declare enum Kind {
|
|
27
27
|
plain = 0,
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
28
|
+
atomicAction = 1,
|
|
29
|
+
reactiveProcess = 2,
|
|
30
|
+
cachedResult = 3
|
|
31
31
|
}
|
|
32
32
|
export declare enum Reentrance {
|
|
33
33
|
preventWithError = 1,
|
|
@@ -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["atomicAction"] = 1] = "atomicAction";
|
|
6
|
+
Kind[Kind["reactiveProcess"] = 2] = "reactiveProcess";
|
|
7
|
+
Kind[Kind["cachedResult"] = 3] = "cachedResult";
|
|
8
8
|
})(Kind || (Kind = {}));
|
|
9
9
|
export var Reentrance;
|
|
10
10
|
(function (Reentrance) {
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { F } from "./util/Utils.js";
|
|
2
2
|
import { ObservableObject } from "./core/Mvcc.js";
|
|
3
|
-
export declare class
|
|
3
|
+
export declare class ReactiveProcess<T> extends ObservableObject {
|
|
4
4
|
protected action: F<T>;
|
|
5
5
|
constructor(action: F<T>);
|
|
6
6
|
protected launch(): T;
|
|
@@ -8,8 +8,8 @@ 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 {
|
|
12
|
-
export class
|
|
11
|
+
import { reactiveProcess } from "./ReactiveSystem.js";
|
|
12
|
+
export class ReactiveProcess extends ObservableObject {
|
|
13
13
|
constructor(action) {
|
|
14
14
|
super();
|
|
15
15
|
this.action = action;
|
|
@@ -19,8 +19,8 @@ export class Reaction extends ObservableObject {
|
|
|
19
19
|
}
|
|
20
20
|
}
|
|
21
21
|
__decorate([
|
|
22
|
-
|
|
22
|
+
reactiveProcess,
|
|
23
23
|
__metadata("design:type", Function),
|
|
24
24
|
__metadata("design:paramtypes", []),
|
|
25
25
|
__metadata("design:returntype", Object)
|
|
26
|
-
],
|
|
26
|
+
], ReactiveProcess.prototype, "launch", null);
|
|
@@ -17,16 +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 atomically<T>(func: F<T>, ...args: any[]): T;
|
|
21
|
+
export declare function atomically<T>(options: SnapshotOptions, func: F<T>, ...args: any[]): T;
|
|
20
22
|
export declare function nonreactive<T>(func: F<T>, ...args: any[]): T;
|
|
21
|
-
export declare function nonreactive2<T>(options: SnapshotOptions, func: F<T>, ...args: any[]): T;
|
|
22
|
-
export declare function nonreactive2<T>(func: F<T>, ...args: any[]): T;
|
|
23
23
|
export declare function sensitive<T>(sensitivity: boolean, func: F<T>, ...args: any[]): T;
|
|
24
24
|
export declare function contextually<T>(p: Promise<T>): Promise<T>;
|
|
25
|
-
export declare function atomicAction<T>(func: F<T>, ...args: any[]): T;
|
|
26
|
-
export declare function atomicAction<T>(options: SnapshotOptions, func: F<T>, ...args: any[]): T;
|
|
27
|
-
export declare function atomicAction(proto: object, prop: PropertyKey, pd: PropertyDescriptor): any;
|
|
28
25
|
export declare function unobservable(proto: object, prop: PropertyKey): any;
|
|
29
26
|
export declare function observable(proto: object, prop: PropertyKey): any;
|
|
30
|
-
export declare function
|
|
31
|
-
export declare function
|
|
27
|
+
export declare function atomicAction(proto: object, prop: PropertyKey, pd: PropertyDescriptor): any;
|
|
28
|
+
export declare function reactiveProcess(proto: object, prop: PropertyKey, pd: PropertyDescriptor): any;
|
|
29
|
+
export declare function cachedResult(proto: object, prop: PropertyKey, pd: PropertyDescriptor): any;
|
|
32
30
|
export declare function options(value: Partial<MemberOptions>): F<any>;
|
|
@@ -22,53 +22,28 @@ 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 OperationImpl.proceedWithinGivenLaunch(undefined, func, ...args);
|
|
27
|
-
}
|
|
28
|
-
export function nonreactive2(p1, p2, p3) {
|
|
29
|
-
if (p1 instanceof Function) {
|
|
30
|
-
return OperationImpl.proceedWithinGivenLaunch(undefined, () => {
|
|
31
|
-
if (p2 !== undefined)
|
|
32
|
-
return Transaction.run(null, p1, ...p2);
|
|
33
|
-
else
|
|
34
|
-
return Transaction.run(null, p1);
|
|
35
|
-
});
|
|
36
|
-
}
|
|
37
|
-
else {
|
|
38
|
-
return OperationImpl.proceedWithinGivenLaunch(undefined, () => {
|
|
39
|
-
if (p3 !== undefined)
|
|
40
|
-
return Transaction.run(p1, p2, ...p3);
|
|
41
|
-
else
|
|
42
|
-
return Transaction.run(p1, p2);
|
|
43
|
-
});
|
|
44
|
-
}
|
|
45
|
-
}
|
|
46
|
-
export function sensitive(sensitivity, func, ...args) {
|
|
47
|
-
return Mvcc.sensitive(sensitivity, func, ...args);
|
|
48
|
-
}
|
|
49
|
-
export function contextually(p) {
|
|
50
|
-
throw new Error("not implemented yet");
|
|
51
|
-
}
|
|
52
|
-
export function atomicAction(p1, p2, p3) {
|
|
25
|
+
export function atomically(p1, p2, p3) {
|
|
53
26
|
if (p1 instanceof Function) {
|
|
54
27
|
if (p2 !== undefined)
|
|
55
28
|
return Transaction.run(null, p1, ...p2);
|
|
56
29
|
else
|
|
57
30
|
return Transaction.run(null, p1);
|
|
58
31
|
}
|
|
59
|
-
else
|
|
32
|
+
else {
|
|
60
33
|
if (p3 !== undefined)
|
|
61
34
|
return Transaction.run(p1, p2, ...p3);
|
|
62
35
|
else
|
|
63
36
|
return Transaction.run(p1, p2);
|
|
64
37
|
}
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
38
|
+
}
|
|
39
|
+
export function nonreactive(func, ...args) {
|
|
40
|
+
return OperationImpl.proceedWithinGivenLaunch(undefined, func, ...args);
|
|
41
|
+
}
|
|
42
|
+
export function sensitive(sensitivity, func, ...args) {
|
|
43
|
+
return Mvcc.sensitive(sensitivity, func, ...args);
|
|
44
|
+
}
|
|
45
|
+
export function contextually(p) {
|
|
46
|
+
throw new Error("not implemented yet");
|
|
72
47
|
}
|
|
73
48
|
export function unobservable(proto, prop) {
|
|
74
49
|
return Mvcc.decorateData(false, proto, prop);
|
|
@@ -76,21 +51,28 @@ export function unobservable(proto, prop) {
|
|
|
76
51
|
export function observable(proto, prop) {
|
|
77
52
|
return Mvcc.decorateData(true, proto, prop);
|
|
78
53
|
}
|
|
79
|
-
export function
|
|
54
|
+
export function atomicAction(proto, prop, pd) {
|
|
55
|
+
const opts = {
|
|
56
|
+
kind: Kind.atomicAction,
|
|
57
|
+
isolation: Isolation.joinToCurrentTransaction,
|
|
58
|
+
};
|
|
59
|
+
return Mvcc.decorateOperation(true, atomicAction, opts, proto, prop, pd);
|
|
60
|
+
}
|
|
61
|
+
export function reactiveProcess(proto, prop, pd) {
|
|
80
62
|
const opts = {
|
|
81
|
-
kind: Kind.
|
|
63
|
+
kind: Kind.reactiveProcess,
|
|
82
64
|
isolation: Isolation.joinAsNestedTransaction,
|
|
83
65
|
throttling: -1,
|
|
84
66
|
};
|
|
85
|
-
return Mvcc.decorateOperation(true,
|
|
67
|
+
return Mvcc.decorateOperation(true, reactiveProcess, opts, proto, prop, pd);
|
|
86
68
|
}
|
|
87
|
-
export function
|
|
69
|
+
export function cachedResult(proto, prop, pd) {
|
|
88
70
|
const opts = {
|
|
89
|
-
kind: Kind.
|
|
71
|
+
kind: Kind.cachedResult,
|
|
90
72
|
isolation: Isolation.joinToCurrentTransaction,
|
|
91
73
|
noSideEffects: true,
|
|
92
74
|
};
|
|
93
|
-
return Mvcc.decorateOperation(true,
|
|
75
|
+
return Mvcc.decorateOperation(true, cachedResult, opts, proto, prop, pd);
|
|
94
76
|
}
|
|
95
77
|
export function options(value) {
|
|
96
78
|
return Mvcc.decorateOperationParametrized(options, value);
|
package/build/dist/source/Ref.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { atomically, nonreactive } from "./ReactiveSystem.js";
|
|
2
2
|
export function refs(owner) {
|
|
3
3
|
return new Proxy(owner, RefGettingProxy);
|
|
4
4
|
}
|
|
@@ -52,7 +52,7 @@ export class ToggleRef extends Ref {
|
|
|
52
52
|
toggle() {
|
|
53
53
|
const o = this.owner;
|
|
54
54
|
const p = this.name;
|
|
55
|
-
|
|
55
|
+
atomically({ hint: `toggle ${o.constructor.name}.${p}` }, () => {
|
|
56
56
|
const v = o[p];
|
|
57
57
|
const isOn = v === this.valueOn || (v instanceof Ref && this.valueOn instanceof Ref &&
|
|
58
58
|
Ref.sameRefs(v, this.valueOn));
|
|
@@ -16,8 +16,9 @@ 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 {
|
|
20
|
-
export {
|
|
19
|
+
export { atomically, nonreactive, sensitive, contextually } from "./ReactiveSystem.js";
|
|
20
|
+
export { ReactiveSystem, observable, unobservable, atomicAction, reactiveProcess, cachedResult, options } from "./ReactiveSystem.js";
|
|
21
|
+
export { ReactiveProcess } from "./Reaction.js";
|
|
21
22
|
export { ReactiveNode, Mode, Priority, BaseDriver, ReactiveNodeVariable } from "./core/ReactiveNode.js";
|
|
22
23
|
export type { Script, ScriptAsync, Handler, ReactiveNodeDecl, ReactiveNodeDriver, ReactiveNodeContext } from "./core/ReactiveNode.js";
|
|
23
24
|
export { Clock } from "./Clock.js";
|
package/build/dist/source/api.js
CHANGED
|
@@ -12,7 +12,8 @@ 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 {
|
|
16
|
-
export {
|
|
15
|
+
export { atomically, nonreactive, sensitive, contextually } from "./ReactiveSystem.js";
|
|
16
|
+
export { ReactiveSystem, observable, unobservable, atomicAction, reactiveProcess, cachedResult, options } from "./ReactiveSystem.js";
|
|
17
|
+
export { ReactiveProcess } from "./Reaction.js";
|
|
17
18
|
export { ReactiveNode, Mode, Priority, BaseDriver, ReactiveNodeVariable } from "./core/ReactiveNode.js";
|
|
18
19
|
export { Clock } from "./Clock.js";
|
|
@@ -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.atomicAction && 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.atomicAction || !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.atomicAction)
|
|
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.reactiveProcess;
|
|
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.reactiveProcess)
|
|
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.reactiveProcess)
|
|
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.atomicAction ||
|
|
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.atomicAction) {
|
|
473
473
|
const launch = Launch.current;
|
|
474
|
-
if (launch && launch.options.kind !== Kind.
|
|
474
|
+
if (launch && launch.options.kind !== Kind.atomicAction &&
|
|
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.reactiveProcess && 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.reactiveProcess && 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, unobservable,
|
|
25
|
+
import { ReactiveSystem, options, unobservable, reactiveProcess, atomically, nonreactive } from "../ReactiveSystem.js";
|
|
26
26
|
export var Mode;
|
|
27
27
|
(function (Mode) {
|
|
28
28
|
Mode[Mode["default"] = 0] = "default";
|
|
@@ -339,7 +339,7 @@ class ReactiveNodeImpl extends ReactiveNode {
|
|
|
339
339
|
node.outer = owner;
|
|
340
340
|
else
|
|
341
341
|
node.outer = owner.outer;
|
|
342
|
-
|
|
342
|
+
atomically({ 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
|
+
reactiveProcess,
|
|
363
363
|
options({
|
|
364
364
|
reentrance: Reentrance.cancelAndWaitPrevious,
|
|
365
365
|
allowObsoleteToFinish: true,
|
|
@@ -546,7 +546,7 @@ function triggerFinalization(slot, isLeader, individual) {
|
|
|
546
546
|
else
|
|
547
547
|
gFirstToDispose = gLastToDispose = slot;
|
|
548
548
|
if (gFirstToDispose === slot)
|
|
549
|
-
|
|
549
|
+
atomically({ isolation: Isolation.disjoinForInternalDisposal, hint: `runDisposalLoop(initiator=${slot.instance.key})` }, () => {
|
|
550
550
|
void runDisposalLoop().then(NOP, error => console.log(error));
|
|
551
551
|
});
|
|
552
552
|
}
|