reactronic 0.22.308 → 0.22.311
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/LICENSE +202 -21
- package/README.md +3 -4
- package/build/dist/source/Buffer.d.ts +8 -0
- package/build/dist/source/Buffer.js +4 -0
- package/build/dist/source/Controller.d.ts +12 -0
- package/build/dist/source/Controller.js +2 -0
- package/build/dist/source/Logging.d.ts +38 -0
- package/build/dist/source/Logging.js +110 -0
- package/build/dist/source/Options.d.ts +38 -0
- package/build/dist/source/Options.js +17 -0
- package/build/dist/source/Ref.d.ts +34 -0
- package/build/dist/source/Ref.js +85 -0
- package/build/dist/source/Rx.d.ts +27 -0
- package/build/dist/source/Rx.js +47 -0
- package/build/dist/source/Worker.d.ts +8 -0
- package/build/dist/source/Worker.js +1 -0
- package/build/dist/source/api.d.ts +14 -0
- package/build/dist/source/api.js +13 -0
- package/build/dist/source/impl/Changeset.d.ts +60 -0
- package/build/dist/source/impl/Changeset.js +356 -0
- package/build/dist/source/impl/Data.d.ts +60 -0
- package/build/dist/source/impl/Data.js +44 -0
- package/build/dist/source/impl/Hooks.d.ts +96 -0
- package/build/dist/source/impl/Hooks.js +302 -0
- package/build/dist/source/impl/Journal.d.ts +34 -0
- package/build/dist/source/impl/Journal.js +144 -0
- package/build/dist/source/impl/Meta.d.ts +13 -0
- package/build/dist/source/impl/Meta.js +29 -0
- package/build/dist/source/impl/Monitor.d.ts +32 -0
- package/build/dist/source/impl/Monitor.js +92 -0
- package/build/dist/source/impl/Operation.d.ts +93 -0
- package/build/dist/source/impl/Operation.js +716 -0
- package/build/dist/source/impl/Transaction.d.ts +30 -0
- package/build/dist/source/impl/Transaction.js +309 -0
- package/build/dist/source/util/Dbg.d.ts +15 -0
- package/build/dist/source/util/Dbg.js +89 -0
- package/build/dist/source/util/Sealant.d.ts +14 -0
- package/build/dist/source/util/Sealant.js +26 -0
- package/build/dist/source/util/SealedArray.d.ts +16 -0
- package/build/dist/source/util/SealedArray.js +24 -0
- package/build/dist/source/util/SealedMap.d.ts +13 -0
- package/build/dist/source/util/SealedMap.js +17 -0
- package/build/dist/source/util/SealedSet.d.ts +13 -0
- package/build/dist/source/util/SealedSet.js +17 -0
- package/build/dist/source/util/Utils.d.ts +9 -0
- package/build/dist/source/util/Utils.js +55 -0
- package/package.json +14 -14
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
import { Log } from './util/Dbg';
|
|
2
|
+
import { Kind } from './Options';
|
|
3
|
+
import { Meta, ObjectHandle } from './impl/Data';
|
|
4
|
+
import { Changeset } from './impl/Changeset';
|
|
5
|
+
import { Hooks } from './impl/Hooks';
|
|
6
|
+
import { OperationController } from './impl/Operation';
|
|
7
|
+
export class Rx {
|
|
8
|
+
static getRevisionOf(obj) { return obj[Meta.Revision]; }
|
|
9
|
+
static why(brief = false) { return brief ? OperationController.briefWhy() : OperationController.why(); }
|
|
10
|
+
static getController(method) { return OperationController.of(method); }
|
|
11
|
+
static pullLastResult(method, args) { return Rx.getController(method).pullLastResult(args); }
|
|
12
|
+
static configureCurrentOperation(options) { return OperationController.configureImpl(undefined, options); }
|
|
13
|
+
static takeSnapshot(obj) { return Changeset.takeSnapshot(obj); }
|
|
14
|
+
static dispose(obj) { Changeset.dispose(obj); }
|
|
15
|
+
static get reactionsAutoStartDisabled() { return Hooks.reactionsAutoStartDisabled; }
|
|
16
|
+
static set reactionsAutoStartDisabled(value) { Hooks.reactionsAutoStartDisabled = value; }
|
|
17
|
+
static get isLogging() { return Log.isOn; }
|
|
18
|
+
static get loggingOptions() { return Log.opt; }
|
|
19
|
+
static setLoggingMode(isOn, options) { Log.setMode(isOn, options); }
|
|
20
|
+
static setLoggingHint(obj, name) { Hooks.setHint(obj, name); }
|
|
21
|
+
static getLoggingHint(obj, full = false) { return ObjectHandle.getHint(obj, full); }
|
|
22
|
+
static setProfilingMode(isOn, options) { Hooks.setProfilingMode(isOn, options); }
|
|
23
|
+
}
|
|
24
|
+
export function nonreactive(func, ...args) {
|
|
25
|
+
return OperationController.runWithin(undefined, func, ...args);
|
|
26
|
+
}
|
|
27
|
+
export function sensitive(sensitivity, func, ...args) {
|
|
28
|
+
return Hooks.sensitive(sensitivity, func, ...args);
|
|
29
|
+
}
|
|
30
|
+
export function isnonreactive(proto, prop) {
|
|
31
|
+
return Hooks.decorateData(false, proto, prop);
|
|
32
|
+
}
|
|
33
|
+
export function transaction(proto, prop, pd) {
|
|
34
|
+
const opts = { kind: Kind.Transaction };
|
|
35
|
+
return Hooks.decorateOperation(true, transaction, opts, proto, prop, pd);
|
|
36
|
+
}
|
|
37
|
+
export function reaction(proto, prop, pd) {
|
|
38
|
+
const opts = { kind: Kind.Reaction, throttling: -1 };
|
|
39
|
+
return Hooks.decorateOperation(true, reaction, opts, proto, prop, pd);
|
|
40
|
+
}
|
|
41
|
+
export function cached(proto, prop, pd) {
|
|
42
|
+
const opts = { kind: Kind.Cache, noSideEffects: true };
|
|
43
|
+
return Hooks.decorateOperation(true, cached, opts, proto, prop, pd);
|
|
44
|
+
}
|
|
45
|
+
export function options(value) {
|
|
46
|
+
return Hooks.decorateOperationParametrized(options, value);
|
|
47
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
export { all, pause } from './util/Utils';
|
|
2
|
+
export { SealedArray } from './util/SealedArray';
|
|
3
|
+
export { SealedMap } from './util/SealedMap';
|
|
4
|
+
export { SealedSet } from './util/SealedSet';
|
|
5
|
+
export { MemberOptions, SnapshotOptions, Kind, Reentrance, LoggingOptions, ProfilingOptions, LoggingLevel } from './Options';
|
|
6
|
+
export { Worker } from './Worker';
|
|
7
|
+
export { Controller } from './Controller';
|
|
8
|
+
export { Ref, ToggleRef, BoolOnly, GivenTypeOnly } from './Ref';
|
|
9
|
+
export { ReactiveObject, ReactiveArray, ReactiveMap } from './impl/Hooks';
|
|
10
|
+
export { Changeset } from './impl/Changeset';
|
|
11
|
+
export { Transaction } from './impl/Transaction';
|
|
12
|
+
export { Monitor } from './impl/Monitor';
|
|
13
|
+
export { Journal } from './impl/Journal';
|
|
14
|
+
export { Rx, nonreactive, sensitive, isnonreactive, transaction, reaction, cached, options } from './Rx';
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
export { all, pause } from './util/Utils';
|
|
2
|
+
export { SealedArray } from './util/SealedArray';
|
|
3
|
+
export { SealedMap } from './util/SealedMap';
|
|
4
|
+
export { SealedSet } from './util/SealedSet';
|
|
5
|
+
export { Kind, Reentrance, LoggingLevel } from './Options';
|
|
6
|
+
export { Controller } from './Controller';
|
|
7
|
+
export { Ref, ToggleRef } from './Ref';
|
|
8
|
+
export { ReactiveObject, ReactiveArray, ReactiveMap } from './impl/Hooks';
|
|
9
|
+
export { Changeset } from './impl/Changeset';
|
|
10
|
+
export { Transaction } from './impl/Transaction';
|
|
11
|
+
export { Monitor } from './impl/Monitor';
|
|
12
|
+
export { Journal } from './impl/Journal';
|
|
13
|
+
export { Rx, nonreactive, sensitive, isnonreactive, transaction, reaction, cached, options } from './Rx';
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
import { Kind, SnapshotOptions } from '../Options';
|
|
2
|
+
import { AbstractChangeset, ObjectSnapshot, MemberName, ObjectHandle, Subscription, Subscriber } from './Data';
|
|
3
|
+
export declare const MAX_REVISION: number;
|
|
4
|
+
export declare const UNDEFINED_REVISION: number;
|
|
5
|
+
export declare class Changeset implements AbstractChangeset {
|
|
6
|
+
static idGen: number;
|
|
7
|
+
private static stampGen;
|
|
8
|
+
private static pending;
|
|
9
|
+
private static oldest;
|
|
10
|
+
static garbageCollectionSummaryInterval: number;
|
|
11
|
+
static lastGarbageCollectionSummaryTimestamp: number;
|
|
12
|
+
static totalObjectHandleCount: number;
|
|
13
|
+
static totalObjectSnapshotCount: number;
|
|
14
|
+
readonly id: number;
|
|
15
|
+
readonly options: SnapshotOptions;
|
|
16
|
+
get hint(): string;
|
|
17
|
+
get timestamp(): number;
|
|
18
|
+
private revision;
|
|
19
|
+
private bumper;
|
|
20
|
+
items: Map<ObjectHandle, ObjectSnapshot>;
|
|
21
|
+
reactions: Subscriber[];
|
|
22
|
+
sealed: boolean;
|
|
23
|
+
constructor(options: SnapshotOptions | null);
|
|
24
|
+
static current: () => Changeset;
|
|
25
|
+
static edit: () => Changeset;
|
|
26
|
+
static markUsed: (subscription: Subscription, os: ObjectSnapshot, m: MemberName, h: ObjectHandle, kind: Kind, weak: boolean) => void;
|
|
27
|
+
static markEdited: (oldValue: any, newValue: any, edited: boolean, os: ObjectSnapshot, m: MemberName, h: ObjectHandle) => void;
|
|
28
|
+
static isConflicting: (oldValue: any, newValue: any) => boolean;
|
|
29
|
+
static propagateAllChangesThroughSubscriptions: (changeset: Changeset) => void;
|
|
30
|
+
static revokeAllSubscriptions: (changeset: Changeset) => void;
|
|
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;
|
|
35
|
+
static takeSnapshot<T>(obj: T): T;
|
|
36
|
+
static dispose(obj: any): void;
|
|
37
|
+
static doDispose(ctx: Changeset, h: ObjectHandle): ObjectSnapshot;
|
|
38
|
+
private isNewSnapshotRequired;
|
|
39
|
+
acquire(outer: Changeset): void;
|
|
40
|
+
bumpBy(timestamp: number): void;
|
|
41
|
+
rebase(): ObjectSnapshot[] | undefined;
|
|
42
|
+
private merge;
|
|
43
|
+
applyOrDiscard(error?: any): Array<Subscriber>;
|
|
44
|
+
static sealObjectSnapshot(h: ObjectHandle, os: ObjectSnapshot): void;
|
|
45
|
+
static sealSubscription(subscription: Subscription | symbol, m: MemberName, typeName: string): void;
|
|
46
|
+
static freezeObjectSnapshot(os: ObjectSnapshot): ObjectSnapshot;
|
|
47
|
+
triggerGarbageCollection(): void;
|
|
48
|
+
private unlinkHistory;
|
|
49
|
+
static _init(): void;
|
|
50
|
+
}
|
|
51
|
+
export declare class Dump {
|
|
52
|
+
static valueHint: (value: any, m?: MemberName) => string;
|
|
53
|
+
static obj(h: ObjectHandle | undefined, m?: MemberName | undefined, stamp?: number, snapshotId?: number, originSnapshotId?: number, value?: any): string;
|
|
54
|
+
static snapshot2(h: ObjectHandle, s: AbstractChangeset, m?: MemberName, o?: Subscription): string;
|
|
55
|
+
static snapshot(os: ObjectSnapshot, m?: MemberName): string;
|
|
56
|
+
static conflicts(conflicts: ObjectSnapshot[]): string;
|
|
57
|
+
static conflictingMemberHint(m: MemberName, ours: ObjectSnapshot, theirs: ObjectSnapshot): string;
|
|
58
|
+
}
|
|
59
|
+
export declare const EMPTY_SNAPSHOT: ObjectSnapshot;
|
|
60
|
+
export declare const DefaultSnapshotOptions: SnapshotOptions;
|
|
@@ -0,0 +1,356 @@
|
|
|
1
|
+
import { Utils, UNDEF } from '../util/Utils';
|
|
2
|
+
import { Log, misuse } from '../util/Dbg';
|
|
3
|
+
import { Sealant } from '../util/Sealant';
|
|
4
|
+
import { SealedArray } from '../util/SealedArray';
|
|
5
|
+
import { SealedMap } from '../util/SealedMap';
|
|
6
|
+
import { SealedSet } from '../util/SealedSet';
|
|
7
|
+
import { ObjectSnapshot, ObjectHandle, Subscription, Meta } from './Data';
|
|
8
|
+
export const MAX_REVISION = Number.MAX_SAFE_INTEGER;
|
|
9
|
+
export const UNDEFINED_REVISION = MAX_REVISION - 1;
|
|
10
|
+
Object.defineProperty(ObjectHandle.prototype, '#this', {
|
|
11
|
+
configurable: false, enumerable: false,
|
|
12
|
+
get() {
|
|
13
|
+
const result = {};
|
|
14
|
+
const data = Changeset.current().getRelevantSnapshot(this, '#this').data;
|
|
15
|
+
for (const m in data) {
|
|
16
|
+
const v = data[m];
|
|
17
|
+
if (v instanceof Subscription)
|
|
18
|
+
result[m] = v.content;
|
|
19
|
+
else if (v === Meta.Nonreactive)
|
|
20
|
+
result[m] = this.data[m];
|
|
21
|
+
else
|
|
22
|
+
result[m] = v;
|
|
23
|
+
}
|
|
24
|
+
return result;
|
|
25
|
+
},
|
|
26
|
+
});
|
|
27
|
+
const EMPTY_ARRAY = Object.freeze([]);
|
|
28
|
+
const EMPTY_MAP = Utils.freezeMap(new Map());
|
|
29
|
+
export class Changeset {
|
|
30
|
+
constructor(options) {
|
|
31
|
+
this.id = ++Changeset.idGen;
|
|
32
|
+
this.options = options !== null && options !== void 0 ? options : DefaultSnapshotOptions;
|
|
33
|
+
this.revision = UNDEFINED_REVISION;
|
|
34
|
+
this.bumper = 100;
|
|
35
|
+
this.items = new Map();
|
|
36
|
+
this.reactions = [];
|
|
37
|
+
this.sealed = false;
|
|
38
|
+
}
|
|
39
|
+
get hint() { var _a; return (_a = this.options.hint) !== null && _a !== void 0 ? _a : 'noname'; }
|
|
40
|
+
get timestamp() { return this.revision; }
|
|
41
|
+
seekSnapshot(h, m) {
|
|
42
|
+
let os = h.editing;
|
|
43
|
+
if (os && os.changeset !== this) {
|
|
44
|
+
os = this.items.get(h);
|
|
45
|
+
if (os)
|
|
46
|
+
h.editing = os;
|
|
47
|
+
}
|
|
48
|
+
if (!os) {
|
|
49
|
+
os = h.head;
|
|
50
|
+
while (os !== EMPTY_SNAPSHOT && os.changeset.timestamp > this.timestamp)
|
|
51
|
+
os = os.former.snapshot;
|
|
52
|
+
}
|
|
53
|
+
return os;
|
|
54
|
+
}
|
|
55
|
+
getRelevantSnapshot(h, m) {
|
|
56
|
+
const r = this.seekSnapshot(h, m);
|
|
57
|
+
if (r === EMPTY_SNAPSHOT)
|
|
58
|
+
throw misuse(`object ${Dump.obj(h)} doesn't exist in snapshot v${this.revision} (${this.hint})`);
|
|
59
|
+
return r;
|
|
60
|
+
}
|
|
61
|
+
getEditableSnapshot(h, m, value, token) {
|
|
62
|
+
let os = this.seekSnapshot(h, m);
|
|
63
|
+
const existing = os.data[m];
|
|
64
|
+
if (existing !== Meta.Nonreactive) {
|
|
65
|
+
if (this.isNewSnapshotRequired(h, os, m, existing, value, token)) {
|
|
66
|
+
this.bumpBy(os.changeset.timestamp);
|
|
67
|
+
const revision = m === Meta.Handle ? 1 : os.revision + 1;
|
|
68
|
+
const data = Object.assign({}, m === Meta.Handle ? value : os.data);
|
|
69
|
+
Meta.set(data, Meta.Handle, h);
|
|
70
|
+
Meta.set(data, Meta.Revision, new Subscription(revision));
|
|
71
|
+
os = new ObjectSnapshot(this, os, data);
|
|
72
|
+
this.items.set(h, os);
|
|
73
|
+
h.editing = os;
|
|
74
|
+
h.editors++;
|
|
75
|
+
if (Log.isOn && Log.opt.write)
|
|
76
|
+
Log.write('║', ' ⎘⎘', `${Dump.obj(h)} - new snapshot is created (revision ${revision})`);
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
else
|
|
80
|
+
os = EMPTY_SNAPSHOT;
|
|
81
|
+
return os;
|
|
82
|
+
}
|
|
83
|
+
static takeSnapshot(obj) {
|
|
84
|
+
return obj[Meta.Handle]['#this'];
|
|
85
|
+
}
|
|
86
|
+
static dispose(obj) {
|
|
87
|
+
const ctx = Changeset.edit();
|
|
88
|
+
const h = Meta.get(obj, Meta.Handle);
|
|
89
|
+
if (h !== undefined)
|
|
90
|
+
Changeset.doDispose(ctx, h);
|
|
91
|
+
}
|
|
92
|
+
static doDispose(ctx, h) {
|
|
93
|
+
const os = ctx.getEditableSnapshot(h, Meta.Revision, Meta.Undefined);
|
|
94
|
+
if (os !== EMPTY_SNAPSHOT)
|
|
95
|
+
os.disposed = true;
|
|
96
|
+
return os;
|
|
97
|
+
}
|
|
98
|
+
isNewSnapshotRequired(h, os, m, existing, value, token) {
|
|
99
|
+
if (this.sealed && os.changeset !== EMPTY_SNAPSHOT.changeset)
|
|
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)})`);
|
|
105
|
+
}
|
|
106
|
+
if (os === EMPTY_SNAPSHOT)
|
|
107
|
+
throw misuse(`member ${Dump.snapshot(os, m)} doesn't exist in snapshot v${this.revision} (${this.hint})`);
|
|
108
|
+
}
|
|
109
|
+
return os.changeset !== this && !this.sealed;
|
|
110
|
+
}
|
|
111
|
+
acquire(outer) {
|
|
112
|
+
if (!this.sealed && this.revision === UNDEFINED_REVISION) {
|
|
113
|
+
const ahead = this.options.token === undefined || outer.revision === UNDEFINED_REVISION;
|
|
114
|
+
this.revision = ahead ? Changeset.stampGen : outer.revision;
|
|
115
|
+
Changeset.pending.push(this);
|
|
116
|
+
if (Changeset.oldest === undefined)
|
|
117
|
+
Changeset.oldest = this;
|
|
118
|
+
if (Log.isOn && Log.opt.transaction)
|
|
119
|
+
Log.write('╔══', `v${this.revision}`, `${this.hint}`);
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
bumpBy(timestamp) {
|
|
123
|
+
if (timestamp > this.bumper)
|
|
124
|
+
this.bumper = timestamp;
|
|
125
|
+
}
|
|
126
|
+
rebase() {
|
|
127
|
+
let conflicts = undefined;
|
|
128
|
+
if (this.items.size > 0) {
|
|
129
|
+
this.items.forEach((os, h) => {
|
|
130
|
+
if (os.former.snapshot !== h.head) {
|
|
131
|
+
const merged = this.merge(h, os);
|
|
132
|
+
if (os.conflicts.size > 0) {
|
|
133
|
+
if (!conflicts)
|
|
134
|
+
conflicts = [];
|
|
135
|
+
conflicts.push(os);
|
|
136
|
+
}
|
|
137
|
+
if (Log.isOn && Log.opt.transaction)
|
|
138
|
+
Log.write('╠╝', '', `${Dump.snapshot2(h, os.changeset)} is merged with ${Dump.snapshot2(h, h.head.changeset)} among ${merged} properties with ${os.conflicts.size} conflicts.`);
|
|
139
|
+
}
|
|
140
|
+
});
|
|
141
|
+
if (this.options.token === undefined) {
|
|
142
|
+
if (this.bumper > 100) {
|
|
143
|
+
this.bumper = this.revision;
|
|
144
|
+
this.revision = ++Changeset.stampGen;
|
|
145
|
+
}
|
|
146
|
+
else
|
|
147
|
+
this.revision = this.bumper + 1;
|
|
148
|
+
}
|
|
149
|
+
else {
|
|
150
|
+
this.revision = this.bumper;
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
return conflicts;
|
|
154
|
+
}
|
|
155
|
+
merge(h, ours) {
|
|
156
|
+
let counter = 0;
|
|
157
|
+
const head = h.head;
|
|
158
|
+
const headDisposed = head.disposed;
|
|
159
|
+
const oursDisposed = ours.disposed;
|
|
160
|
+
const merged = Object.assign({}, head.data);
|
|
161
|
+
ours.changes.forEach((o, m) => {
|
|
162
|
+
counter++;
|
|
163
|
+
merged[m] = ours.data[m];
|
|
164
|
+
if (headDisposed || oursDisposed) {
|
|
165
|
+
if (headDisposed !== oursDisposed) {
|
|
166
|
+
if (headDisposed || this.options.standalone !== 'disposal') {
|
|
167
|
+
if (Log.isOn && Log.opt.change)
|
|
168
|
+
Log.write('║╠', '', `${Dump.snapshot2(h, ours.changeset, m)} <> ${Dump.snapshot2(h, head.changeset, m)}`, 0, ' *** CONFLICT ***');
|
|
169
|
+
ours.conflicts.set(m, head);
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
else {
|
|
174
|
+
const conflict = Changeset.isConflicting(head.data[m], ours.former.snapshot.data[m]);
|
|
175
|
+
if (conflict)
|
|
176
|
+
ours.conflicts.set(m, head);
|
|
177
|
+
if (Log.isOn && Log.opt.change)
|
|
178
|
+
Log.write('║╠', '', `${Dump.snapshot2(h, ours.changeset, m)} ${conflict ? '<>' : '=='} ${Dump.snapshot2(h, head.changeset, m)}`, 0, conflict ? ' *** CONFLICT ***' : undefined);
|
|
179
|
+
}
|
|
180
|
+
});
|
|
181
|
+
Utils.copyAllMembers(merged, ours.data);
|
|
182
|
+
ours.former.snapshot = head;
|
|
183
|
+
return counter;
|
|
184
|
+
}
|
|
185
|
+
applyOrDiscard(error) {
|
|
186
|
+
this.sealed = true;
|
|
187
|
+
this.items.forEach((os, h) => {
|
|
188
|
+
Changeset.sealObjectSnapshot(h, os);
|
|
189
|
+
h.editors--;
|
|
190
|
+
if (h.editors === 0)
|
|
191
|
+
h.editing = undefined;
|
|
192
|
+
if (!error) {
|
|
193
|
+
h.head = os;
|
|
194
|
+
if (Changeset.garbageCollectionSummaryInterval < Number.MAX_SAFE_INTEGER) {
|
|
195
|
+
Changeset.totalObjectSnapshotCount++;
|
|
196
|
+
if (os.former.snapshot === EMPTY_SNAPSHOT)
|
|
197
|
+
Changeset.totalObjectHandleCount++;
|
|
198
|
+
}
|
|
199
|
+
}
|
|
200
|
+
});
|
|
201
|
+
if (Log.isOn) {
|
|
202
|
+
if (Log.opt.change && !error) {
|
|
203
|
+
this.items.forEach((os, h) => {
|
|
204
|
+
const members = [];
|
|
205
|
+
os.changes.forEach((o, m) => members.push(m.toString()));
|
|
206
|
+
const s = members.join(', ');
|
|
207
|
+
Log.write('║', '√', `${Dump.snapshot2(h, os.changeset)} (${s}) is ${os.former.snapshot === EMPTY_SNAPSHOT ? 'constructed' : `applied on top of ${Dump.snapshot2(h, os.former.snapshot.changeset)}`}`);
|
|
208
|
+
});
|
|
209
|
+
}
|
|
210
|
+
if (Log.opt.transaction)
|
|
211
|
+
Log.write(this.revision < UNDEFINED_REVISION ? '╚══' : '═══', `v${this.revision}`, `${this.hint} - ${error ? 'CANCEL' : 'APPLY'}(${this.items.size})${error ? ` - ${error}` : ''}`);
|
|
212
|
+
}
|
|
213
|
+
if (!error)
|
|
214
|
+
Changeset.propagateAllChangesThroughSubscriptions(this);
|
|
215
|
+
return this.reactions;
|
|
216
|
+
}
|
|
217
|
+
static sealObjectSnapshot(h, os) {
|
|
218
|
+
if (!os.disposed)
|
|
219
|
+
os.changes.forEach((o, m) => Changeset.sealSubscription(os.data[m], m, h.proxy.constructor.name));
|
|
220
|
+
else
|
|
221
|
+
for (const m in os.former.snapshot.data)
|
|
222
|
+
os.data[m] = Meta.Undefined;
|
|
223
|
+
if (Log.isOn)
|
|
224
|
+
Changeset.freezeObjectSnapshot(os);
|
|
225
|
+
}
|
|
226
|
+
static sealSubscription(subscription, m, typeName) {
|
|
227
|
+
if (subscription instanceof Subscription) {
|
|
228
|
+
const value = subscription.content;
|
|
229
|
+
if (value !== undefined && value !== null) {
|
|
230
|
+
const sealedType = Object.getPrototypeOf(value)[Sealant.SealedType];
|
|
231
|
+
if (sealedType)
|
|
232
|
+
subscription.content = Sealant.seal(value, sealedType, typeName, m);
|
|
233
|
+
}
|
|
234
|
+
}
|
|
235
|
+
}
|
|
236
|
+
static freezeObjectSnapshot(os) {
|
|
237
|
+
Object.freeze(os.data);
|
|
238
|
+
Utils.freezeSet(os.changes);
|
|
239
|
+
Utils.freezeMap(os.conflicts);
|
|
240
|
+
return os;
|
|
241
|
+
}
|
|
242
|
+
triggerGarbageCollection() {
|
|
243
|
+
if (this.revision !== 0) {
|
|
244
|
+
if (this === Changeset.oldest) {
|
|
245
|
+
const p = Changeset.pending;
|
|
246
|
+
p.sort((a, b) => a.revision - b.revision);
|
|
247
|
+
let i = 0;
|
|
248
|
+
while (i < p.length && p[i].sealed) {
|
|
249
|
+
p[i].unlinkHistory();
|
|
250
|
+
i++;
|
|
251
|
+
}
|
|
252
|
+
Changeset.pending = p.slice(i);
|
|
253
|
+
Changeset.oldest = Changeset.pending[0];
|
|
254
|
+
const now = Date.now();
|
|
255
|
+
if (now - Changeset.lastGarbageCollectionSummaryTimestamp > Changeset.garbageCollectionSummaryInterval) {
|
|
256
|
+
Log.write('', '[G]', `Total object/snapshot count: ${Changeset.totalObjectHandleCount}/${Changeset.totalObjectSnapshotCount}`);
|
|
257
|
+
Changeset.lastGarbageCollectionSummaryTimestamp = now;
|
|
258
|
+
}
|
|
259
|
+
}
|
|
260
|
+
}
|
|
261
|
+
}
|
|
262
|
+
unlinkHistory() {
|
|
263
|
+
if (Log.isOn && Log.opt.gc)
|
|
264
|
+
Log.write('', '[G]', `Dismiss history below v${this.revision}t${this.id} (${this.hint})`);
|
|
265
|
+
this.items.forEach((os, h) => {
|
|
266
|
+
if (Log.isOn && Log.opt.gc && os.former.snapshot !== EMPTY_SNAPSHOT)
|
|
267
|
+
Log.write(' ', ' ', `${Dump.snapshot2(h, os.former.snapshot.changeset)} is ready for GC because overwritten by ${Dump.snapshot2(h, os.changeset)}`);
|
|
268
|
+
if (Changeset.garbageCollectionSummaryInterval < Number.MAX_SAFE_INTEGER) {
|
|
269
|
+
if (os.former.snapshot !== EMPTY_SNAPSHOT)
|
|
270
|
+
Changeset.totalObjectSnapshotCount--;
|
|
271
|
+
if (os.disposed)
|
|
272
|
+
Changeset.totalObjectHandleCount--;
|
|
273
|
+
}
|
|
274
|
+
os.former.snapshot = EMPTY_SNAPSHOT;
|
|
275
|
+
});
|
|
276
|
+
this.items = EMPTY_MAP;
|
|
277
|
+
this.reactions = EMPTY_ARRAY;
|
|
278
|
+
if (Log.isOn)
|
|
279
|
+
Object.freeze(this);
|
|
280
|
+
}
|
|
281
|
+
static _init() {
|
|
282
|
+
const boot = EMPTY_SNAPSHOT.changeset;
|
|
283
|
+
boot.acquire(boot);
|
|
284
|
+
boot.applyOrDiscard();
|
|
285
|
+
boot.triggerGarbageCollection();
|
|
286
|
+
Changeset.freezeObjectSnapshot(EMPTY_SNAPSHOT);
|
|
287
|
+
Changeset.idGen = 100;
|
|
288
|
+
Changeset.stampGen = 101;
|
|
289
|
+
Changeset.oldest = undefined;
|
|
290
|
+
SealedArray.prototype;
|
|
291
|
+
SealedMap.prototype;
|
|
292
|
+
SealedSet.prototype;
|
|
293
|
+
}
|
|
294
|
+
}
|
|
295
|
+
Changeset.idGen = -1;
|
|
296
|
+
Changeset.stampGen = 1;
|
|
297
|
+
Changeset.pending = [];
|
|
298
|
+
Changeset.oldest = undefined;
|
|
299
|
+
Changeset.garbageCollectionSummaryInterval = Number.MAX_SAFE_INTEGER;
|
|
300
|
+
Changeset.lastGarbageCollectionSummaryTimestamp = Date.now();
|
|
301
|
+
Changeset.totalObjectHandleCount = 0;
|
|
302
|
+
Changeset.totalObjectSnapshotCount = 0;
|
|
303
|
+
Changeset.current = UNDEF;
|
|
304
|
+
Changeset.edit = UNDEF;
|
|
305
|
+
Changeset.markUsed = UNDEF;
|
|
306
|
+
Changeset.markEdited = UNDEF;
|
|
307
|
+
Changeset.isConflicting = UNDEF;
|
|
308
|
+
Changeset.propagateAllChangesThroughSubscriptions = (changeset) => { };
|
|
309
|
+
Changeset.revokeAllSubscriptions = (changeset) => { };
|
|
310
|
+
Changeset.enqueueReactionsToRun = (reactions) => { };
|
|
311
|
+
export class Dump {
|
|
312
|
+
static obj(h, m, stamp, snapshotId, originSnapshotId, value) {
|
|
313
|
+
const member = m !== undefined ? `.${m.toString()}` : '';
|
|
314
|
+
let result;
|
|
315
|
+
if (h !== undefined) {
|
|
316
|
+
const v = value !== undefined && value !== Meta.Undefined ? `[=${Dump.valueHint(value)}]` : '';
|
|
317
|
+
if (stamp === undefined)
|
|
318
|
+
result = `${h.hint}${member}${v} #${h.id}`;
|
|
319
|
+
else
|
|
320
|
+
result = `${h.hint}${member}${v} #${h.id}t${snapshotId}v${stamp}${originSnapshotId !== undefined && originSnapshotId !== 0 ? `t${originSnapshotId}` : ''}`;
|
|
321
|
+
}
|
|
322
|
+
else
|
|
323
|
+
result = `boot${member}`;
|
|
324
|
+
return result;
|
|
325
|
+
}
|
|
326
|
+
static snapshot2(h, s, m, o) {
|
|
327
|
+
var _a;
|
|
328
|
+
return Dump.obj(h, m, s.timestamp, s.id, o === null || o === void 0 ? void 0 : o.originSnapshotId, (_a = o === null || o === void 0 ? void 0 : o.content) !== null && _a !== void 0 ? _a : Meta.Undefined);
|
|
329
|
+
}
|
|
330
|
+
static snapshot(os, m) {
|
|
331
|
+
const h = Meta.get(os.data, Meta.Handle);
|
|
332
|
+
const value = m !== undefined ? os.data[m] : undefined;
|
|
333
|
+
return Dump.obj(h, m, os.changeset.timestamp, os.changeset.id, value === null || value === void 0 ? void 0 : value.originSnapshotId);
|
|
334
|
+
}
|
|
335
|
+
static conflicts(conflicts) {
|
|
336
|
+
return conflicts.map(ours => {
|
|
337
|
+
const items = [];
|
|
338
|
+
ours.conflicts.forEach((theirs, m) => {
|
|
339
|
+
items.push(Dump.conflictingMemberHint(m, ours, theirs));
|
|
340
|
+
});
|
|
341
|
+
return items.join(', ');
|
|
342
|
+
}).join(', ');
|
|
343
|
+
}
|
|
344
|
+
static conflictingMemberHint(m, ours, theirs) {
|
|
345
|
+
return `${theirs.changeset.hint} (${Dump.snapshot(theirs, m)})`;
|
|
346
|
+
}
|
|
347
|
+
}
|
|
348
|
+
Dump.valueHint = (value, m) => '???';
|
|
349
|
+
export const EMPTY_SNAPSHOT = new ObjectSnapshot(new Changeset({ hint: '<empty>' }), undefined, {});
|
|
350
|
+
export const DefaultSnapshotOptions = Object.freeze({
|
|
351
|
+
hint: 'noname',
|
|
352
|
+
standalone: false,
|
|
353
|
+
journal: undefined,
|
|
354
|
+
logging: undefined,
|
|
355
|
+
token: undefined,
|
|
356
|
+
});
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
export { Meta } from './Meta';
|
|
2
|
+
export interface AbstractChangeset {
|
|
3
|
+
readonly id: number;
|
|
4
|
+
readonly hint: string;
|
|
5
|
+
readonly timestamp: number;
|
|
6
|
+
readonly sealed: boolean;
|
|
7
|
+
}
|
|
8
|
+
export declare class Subscription {
|
|
9
|
+
content: any;
|
|
10
|
+
subscribers?: Set<Subscriber>;
|
|
11
|
+
get isOperation(): boolean;
|
|
12
|
+
get originSnapshotId(): number | undefined;
|
|
13
|
+
constructor(content: any);
|
|
14
|
+
}
|
|
15
|
+
export declare type StandaloneMode = boolean | 'isolated' | 'disposal';
|
|
16
|
+
export interface Subscriber {
|
|
17
|
+
readonly order: number;
|
|
18
|
+
readonly subscriptions: Map<Subscription, SubscriptionInfo> | undefined;
|
|
19
|
+
readonly obsoleteSince: number;
|
|
20
|
+
hint(nop?: boolean): string;
|
|
21
|
+
markObsoleteDueTo(subscription: Subscription, m: MemberName, changeset: AbstractChangeset, h: ObjectHandle, outer: string, since: number, reactions: Array<Subscriber>): void;
|
|
22
|
+
runIfNotUpToDate(now: boolean, nothrow: boolean): void;
|
|
23
|
+
}
|
|
24
|
+
export declare type MemberName = PropertyKey;
|
|
25
|
+
export interface SubscriptionInfo {
|
|
26
|
+
readonly memberHint: string;
|
|
27
|
+
readonly usageCount: number;
|
|
28
|
+
}
|
|
29
|
+
export declare class ObjectSnapshot {
|
|
30
|
+
readonly changeset: AbstractChangeset;
|
|
31
|
+
readonly former: {
|
|
32
|
+
snapshot: ObjectSnapshot;
|
|
33
|
+
};
|
|
34
|
+
readonly data: any;
|
|
35
|
+
readonly changes: Set<MemberName>;
|
|
36
|
+
readonly conflicts: Map<MemberName, ObjectSnapshot>;
|
|
37
|
+
constructor(changeset: AbstractChangeset, former: ObjectSnapshot | undefined, data: object);
|
|
38
|
+
get revision(): number;
|
|
39
|
+
get disposed(): boolean;
|
|
40
|
+
set disposed(value: boolean);
|
|
41
|
+
}
|
|
42
|
+
export declare class ObjectHandle {
|
|
43
|
+
private static generator;
|
|
44
|
+
readonly id: number;
|
|
45
|
+
readonly data: any;
|
|
46
|
+
readonly proxy: any;
|
|
47
|
+
head: ObjectSnapshot;
|
|
48
|
+
editing?: ObjectSnapshot;
|
|
49
|
+
editors: number;
|
|
50
|
+
hint: string;
|
|
51
|
+
constructor(data: any, proxy: any, handler: ProxyHandler<ObjectHandle>, head: ObjectSnapshot, hint: string);
|
|
52
|
+
static getHint(obj: object, full: boolean): string | undefined;
|
|
53
|
+
}
|
|
54
|
+
export declare type PatchSet = Map<object, Map<MemberName, ValuePatch>>;
|
|
55
|
+
export interface ValuePatch {
|
|
56
|
+
memberName: MemberName;
|
|
57
|
+
patchKind: 'update' | 'add' | 'remove';
|
|
58
|
+
freshValue: any;
|
|
59
|
+
formerValue: any;
|
|
60
|
+
}
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
import { Log } from '../util/Dbg';
|
|
2
|
+
import { Meta } from './Meta';
|
|
3
|
+
export { Meta } from './Meta';
|
|
4
|
+
export class Subscription {
|
|
5
|
+
constructor(content) { this.content = content; }
|
|
6
|
+
get isOperation() { return false; }
|
|
7
|
+
get originSnapshotId() { return 0; }
|
|
8
|
+
}
|
|
9
|
+
export class ObjectSnapshot {
|
|
10
|
+
constructor(changeset, former, data) {
|
|
11
|
+
this.changeset = changeset;
|
|
12
|
+
this.former = { snapshot: former || this };
|
|
13
|
+
this.data = data;
|
|
14
|
+
this.changes = new Set();
|
|
15
|
+
this.conflicts = new Map();
|
|
16
|
+
if (Log.isOn)
|
|
17
|
+
Object.freeze(this);
|
|
18
|
+
}
|
|
19
|
+
get revision() {
|
|
20
|
+
return this.data[Meta.Revision].content;
|
|
21
|
+
}
|
|
22
|
+
get disposed() { return this.revision < 0; }
|
|
23
|
+
set disposed(value) {
|
|
24
|
+
const rev = this.revision;
|
|
25
|
+
if (rev < 0 !== value)
|
|
26
|
+
this.data[Meta.Revision].content = ~rev;
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
export class ObjectHandle {
|
|
30
|
+
constructor(data, proxy, handler, head, hint) {
|
|
31
|
+
this.id = ++ObjectHandle.generator;
|
|
32
|
+
this.data = data;
|
|
33
|
+
this.proxy = proxy || new Proxy(this, handler);
|
|
34
|
+
this.head = head;
|
|
35
|
+
this.editing = undefined;
|
|
36
|
+
this.editors = 0;
|
|
37
|
+
this.hint = hint;
|
|
38
|
+
}
|
|
39
|
+
static getHint(obj, full) {
|
|
40
|
+
const h = Meta.get(obj, Meta.Handle);
|
|
41
|
+
return h !== undefined ? (full ? `${h.hint}#${h.id}` : h.hint) : undefined;
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
ObjectHandle.generator = 19;
|