UNPKG

reactronic

Version:

Reactronic - Transactional Reactive State Management

762 lines (761 loc) 38.7 kB
import { Log, misuse } from "../util/Dbg.js"; import { Kind, Reentrance, Isolation } from "../Enums.js"; import { ObjectHandle, ContentFootprint, Meta } from "./Data.js"; import { Changeset, Dump, EMPTY_OBJECT_VERSION, MAX_REVISION } from "./Changeset.js"; import { Transaction, TransactionImpl } from "./Transaction.js"; import { IndicatorImpl } from "./Indicator.js"; import { Mvcc, OptionsImpl } from "./Mvcc.js"; import { JournalImpl } from "./Journal.js"; const BOOT_ARGS = []; const BOOT_CAUSE = "<boot>"; const EMPTY_HANDLE = new ObjectHandle(undefined, undefined, Mvcc.sx, EMPTY_OBJECT_VERSION, "<boot>"); export class ReactionImpl { configure(options) { return ReactionImpl.configureImpl(this, options); } get options() { return this.peek(undefined).footprint.options; } get nonreactive() { return this.peek(undefined).footprint.content; } get args() { return this.use().footprint.args; } get result() { return this.reuseOrRelaunch(true, undefined).content; } get error() { return this.use().footprint.error; } get stamp() { return this.use().objectVersion.changeset.timestamp; } get isReusable() { return this.use().isReusable; } markObsolete() { Transaction.run({ hint: Log.isOn ? `markObsolete(${Dump.obj(this.ownerHandle, this.fieldKey)})` : "markObsolete()" }, ReactionImpl.markObsolete, this); } pullLastResult(args) { return this.reuseOrRelaunch(true, args).content; } constructor(h, fk) { this.ownerHandle = h; this.fieldKey = fk; } reuseOrRelaunch(weak, args) { let ror = this.peek(args); const ctx = ror.changeset; const footprint = ror.footprint; const opts = footprint.options; if (!ror.isReusable && !ror.objectVersion.disposed && (!weak || footprint.cause === BOOT_CAUSE || !footprint.successor || footprint.successor.transaction.isFinished)) { const isolation = !weak ? opts.isolation : Isolation.disjoinFromOuterTransaction; const token = opts.noSideEffects ? this : undefined; const ror2 = this.relaunch(ror, isolation, opts, token, args); const ctx2 = ror2.footprint.changeset; if (!weak || ctx === ctx2 || (ctx2.sealed && ctx.timestamp >= ctx2.timestamp)) ror = ror2; } else if (Log.isOn && Log.opt.operation && (opts.logging === undefined || opts.logging.operation === undefined || opts.logging.operation === true)) Log.write(Transaction.current.isFinished ? "" : "║", " (=)", `${Dump.snapshot2(ror.footprint.descriptor.ownerHandle, ror.changeset, this.fieldKey)} result is reused from T${ror.footprint.transaction.id}[${ror.footprint.transaction.hint}]`); const t = ror.footprint; Changeset.markUsed(t, ror.objectVersion, this.fieldKey, this.ownerHandle, t.options.kind, weak); return t; } static manageReaction(method) { const ctl = Meta.get(method, Meta.Descriptor); if (!ctl) throw misuse(`given method is not decorated as reactronic one: ${method.name}`); return ctl; } static configureImpl(self, options) { let footprint; if (self) footprint = self.edit().footprint; else footprint = OperationFootprintImpl.current; if (!footprint) throw misuse("reactronic decorator is only applicable to methods"); footprint.options = new OptionsImpl(footprint.options.getter, footprint.options.setter, footprint.options, options, false); if (Log.isOn && Log.opt.write) Log.write("║", " =", `${footprint.hint()}.options are changed`); return footprint.options; } static proceedWithinGivenLaunch(footprint, func, ...args) { let result = undefined; const outer = OperationFootprintImpl.current; try { OperationFootprintImpl.current = footprint; result = func(...args); } catch (e) { if (footprint) footprint.error = e; throw e; } finally { OperationFootprintImpl.current = outer; } return result; } static why() { var _a, _b; return (_b = (_a = OperationFootprintImpl.current) === null || _a === void 0 ? void 0 : _a.why()) !== null && _b !== void 0 ? _b : BOOT_CAUSE; } static briefWhy() { var _a, _b; return (_b = (_a = OperationFootprintImpl.current) === null || _a === void 0 ? void 0 : _a.briefWhy()) !== null && _b !== void 0 ? _b : BOOT_CAUSE; } static dependencies() { const l = OperationFootprintImpl.current; return l ? l.dependencies() : ["RxSystem.dependencies should be called from inside of reactive method"]; } peek(args) { const ctx = Changeset.current(); const ov = ctx.lookupObjectVersion(this.ownerHandle, this.fieldKey, false); const footprint = this.acquireFromObjectVersion(ov, args); const applied = this.ownerHandle.applied.data[this.fieldKey]; const isReusable = footprint.options.kind !== Kind.transaction && footprint.cause !== BOOT_CAUSE && (ctx === footprint.changeset || ctx.timestamp < footprint.obsoleteSince || applied.obsoleteDueTo === undefined) && (!footprint.options.signalArgs || args === undefined || footprint.args.length === args.length && footprint.args.every((t, i) => t === args[i])) || ov.disposed; return { footprint, isReusable, changeset: ctx, objectVersion: ov }; } use() { const ror = this.peek(undefined); Changeset.markUsed(ror.footprint, ror.objectVersion, this.fieldKey, this.ownerHandle, ror.footprint.options.kind, true); return ror; } edit() { const h = this.ownerHandle; const fk = this.fieldKey; const ctx = Changeset.edit(); const ov = ctx.getEditableObjectVersion(h, fk, Meta.Handle, this); let footprint = this.acquireFromObjectVersion(ov, undefined); if (footprint.changeset !== ov.changeset) { const newFootprint = new OperationFootprintImpl(Transaction.current, this, ov.changeset, footprint, false); ov.data[fk] = newFootprint.reenterOver(footprint); ctx.bumpBy(ov.former.objectVersion.changeset.timestamp); Changeset.markEdited(footprint, newFootprint, true, ov, fk, h); footprint = newFootprint; } return { footprint, isReusable: true, changeset: ctx, objectVersion: ov }; } acquireFromObjectVersion(ov, args) { const fk = this.fieldKey; let footprint = ov.data[fk]; if (footprint.descriptor !== this) { if (ov.changeset !== EMPTY_OBJECT_VERSION.changeset) { const hint = Log.isOn ? `${Dump.obj(this.ownerHandle, fk)}/init` : "OperationDescriptor/init"; const isolation = Isolation.joinToCurrentTransaction; footprint = Transaction.run({ hint, isolation, token: this }, () => { const h = this.ownerHandle; let r = Changeset.current().getObjectVersion(h, fk); let newFootprint = r.data[fk]; if (newFootprint.descriptor !== this) { r = Changeset.edit().getEditableObjectVersion(h, fk, Meta.Handle, this); const t = new OperationFootprintImpl(Transaction.current, this, r.changeset, newFootprint, false); if (args) t.args = args; t.cause = BOOT_CAUSE; r.data[fk] = t; Changeset.markEdited(newFootprint, t, true, r, fk, h); newFootprint = t; } return newFootprint; }); } else { const initialFootprint = new OperationFootprintImpl(Transaction.current, this, ov.changeset, footprint, false); if (args) initialFootprint.args = args; initialFootprint.cause = BOOT_CAUSE; ov.data[fk] = initialFootprint; footprint = initialFootprint; if (Log.isOn && Log.opt.write) Log.write("║", " ++", `${Dump.obj(this.ownerHandle, fk)} is initialized (revision ${ov.revision})`); } } return footprint; } relaunch(existing, isolation, options, token, args) { const hint = Log.isOn ? `${Dump.obj(this.ownerHandle, this.fieldKey)}${args && args.length > 0 && (typeof args[0] === "number" || typeof args[0] === "string") ? ` - ${args[0]}` : ""}` : `${Dump.obj(this.ownerHandle, this.fieldKey)}`; let ror = existing; const opts = { hint, isolation, journal: options.journal, logging: options.logging, token }; const result = Transaction.run(opts, (argsx) => { if (!ror.footprint.transaction.isCanceled) { ror = this.edit(); if (Log.isOn && Log.opt.operation) Log.write("║", " o", `${ror.footprint.why()}`); ror.footprint.proceed(this.ownerHandle.proxy, argsx); } else { ror = this.peek(argsx); if (ror.footprint.options.kind === Kind.transaction || !ror.isReusable) { ror = this.edit(); if (Log.isOn && Log.opt.operation) Log.write("║", " o", `${ror.footprint.why()}`); ror.footprint.proceed(this.ownerHandle.proxy, argsx); } } return ror.footprint.result; }, args); ror.footprint.result = result; return ror; } static markObsolete(self) { const ror = self.peek(undefined); const ctx = ror.changeset; const obsolete = ror.footprint.transaction.isFinished ? ctx.obsolete : ror.footprint.transaction.changeset.obsolete; ror.footprint.markObsoleteDueTo(ror.footprint, self.fieldKey, EMPTY_OBJECT_VERSION.changeset, EMPTY_HANDLE, BOOT_CAUSE, ctx.timestamp, obsolete); } } class OperationFootprintImpl extends ContentFootprint { constructor(transaction, descriptor, changeset, former, clone) { super(undefined, 0); this.margin = OperationFootprintImpl.current ? OperationFootprintImpl.current.margin + 1 : 1; this.transaction = transaction; this.descriptor = descriptor; this.changeset = changeset; this.signals = new Map(); if (former instanceof OperationFootprintImpl) { this.options = former.options; this.cause = former.obsoleteDueTo; this.args = former.args; if (clone) { this.lastEditorChangesetId = former.lastEditorChangesetId; this.result = former.result; this.error = former.error; this.started = former.started; this.obsoleteSince = former.obsoleteSince; this.obsoleteDueTo = former.obsoleteDueTo; this.successor = former.successor; } else { this.lastEditorChangesetId = changeset.id; this.result = undefined; this.error = undefined; this.started = 0; this.obsoleteSince = 0; this.obsoleteDueTo = undefined; this.successor = undefined; } } else { this.lastEditorChangesetId = changeset.id; this.options = former; this.cause = undefined; this.args = BOOT_ARGS; this.result = undefined; this.error = undefined; this.started = 0; this.obsoleteSince = 0; this.obsoleteDueTo = undefined; this.successor = undefined; } } get isComputed() { return true; } hint() { return `${Dump.snapshot2(this.descriptor.ownerHandle, this.changeset, this.descriptor.fieldKey, this)}`; } get order() { return this.options.order; } get ["#this#"]() { return `Operation: ${this.why()}`; } clone(t, cs) { return new OperationFootprintImpl(t, this.descriptor, cs, this, true); } why() { let cause; if (this.cause) cause = ` ◀◀ ${this.cause}`; else if (this.descriptor.options.kind === Kind.transaction) cause = " ◀◀ operation"; else cause = ` ◀◀ T${this.changeset.id}[${this.changeset.hint}]`; return `${this.hint()}${cause}`; } briefWhy() { return this.why(); } dependencies() { throw misuse("not implemented yet"); } wrap(func) { const wrappedForOperation = (...args) => { if (Log.isOn && Log.opt.step && this.result) Log.writeAs({ margin2: this.margin }, "║", "‾\\", `${this.hint()} - step in `, 0, " │"); const started = Date.now(); const result = ReactionImpl.proceedWithinGivenLaunch(this, func, ...args); const ms = Date.now() - started; if (Log.isOn && Log.opt.step && this.result) Log.writeAs({ margin2: this.margin }, "║", "_/", `${this.hint()} - step out `, 0, this.started > 0 ? " │" : ""); if (ms > Mvcc.mainThreadBlockingWarningThreshold) Log.write("", "[!]", this.why(), ms, " *** main thread is too busy ***"); return result; }; return wrappedForOperation; } proceed(proxy, args) { if (args) this.args = args; this.obsoleteSince = MAX_REVISION; if (!this.error) ReactionImpl.proceedWithinGivenLaunch(this, OperationFootprintImpl.proceed, this, proxy); else this.result = Promise.reject(this.error); } markObsoleteDueTo(footprint, fk, changeset, h, outer, since, collector) { var _a, _b, _c; if (this.signals !== undefined) { const skip = !footprint.isComputed && changeset.id === this.lastEditorChangesetId; if (!skip) { const why = `${Dump.snapshot2(h, changeset, fk, footprint)} ◀◀ ${outer}`; const isReactive = this.options.kind === Kind.reaction; this.obsoleteDueTo = why; this.obsoleteSince = since; if (Log.isOn && (Log.opt.obsolete || ((_a = this.options.logging) === null || _a === void 0 ? void 0 : _a.obsolete))) Log.write(Log.opt.transaction && !Changeset.current().sealed ? "║" : " ", isReactive ? "█" : "▒", isReactive && changeset === EMPTY_OBJECT_VERSION.changeset ? `${this.hint()} is reactive and will run automatically (order ${this.options.order})` : `${this.hint()} is obsolete due to ${Dump.snapshot2(h, changeset, fk)} since s${since}${isReactive ? ` and will run automatically (order ${this.options.order})` : ""}`); this.stopListeningAllSignals(); if (isReactive) collector.push(this); else (_b = this.listeners) === null || _b === void 0 ? void 0 : _b.forEach(s => s.markObsoleteDueTo(this, this.descriptor.fieldKey, this.changeset, this.descriptor.ownerHandle, why, since, collector)); const tran = this.transaction; if (tran.changeset === changeset) { } else if (!tran.isFinished && this !== footprint && !this.options.allowObsoleteToFinish) tran.cancel(new Error(`T${tran.id}[${tran.hint}] is canceled due to obsolete ${Dump.snapshot2(h, changeset, fk)} changed by T${changeset.id}[${changeset.hint}]`), null); } else if (Log.isOn && (Log.opt.obsolete || ((_c = this.options.logging) === null || _c === void 0 ? void 0 : _c.obsolete))) Log.write(" ", "x", `${this.hint()} is not obsolete due to its own change to ${Dump.snapshot2(h, changeset, fk, footprint)}`); } } relaunchIfNotUpToDate(now, nothrow) { const t = this.options.throttling; const interval = Date.now() + this.started; const hold = t ? t - interval : 0; if (now || hold < 0) { if (this.isNotUpToDate()) { try { const footprint = this.descriptor.reuseOrRelaunch(false, undefined); if (footprint.result instanceof Promise) footprint.result.catch(error => { if (footprint.options.kind === Kind.reaction) misuse(`reactive function ${footprint.hint()} failed and will not run anymore: ${error}`, error); }); } catch (e) { if (!nothrow) throw e; else if (this.options.kind === Kind.reaction) misuse(`reactive ${this.hint()} failed and will not run anymore: ${e}`, e); } } } else if (t < Number.MAX_SAFE_INTEGER) { if (hold > 0) setTimeout(() => this.relaunchIfNotUpToDate(true, true), hold); else this.addToDeferredReactiveFunctions(); } } isNotUpToDate() { return !this.error && (this.options.kind === Kind.transaction || !this.successor || this.successor.transaction.isCanceled); } reenterOver(head) { let error = undefined; const opponent = head.successor; if (opponent && !opponent.transaction.isFinished) { if (Log.isOn && Log.opt.obsolete) Log.write("║", " [!]", `${this.hint()} is trying to re-enter over ${opponent.hint()}`); switch (head.options.reentrance) { case Reentrance.preventWithError: if (!opponent.transaction.isCanceled) throw misuse(`${head.hint()} (${head.why()}) is not reentrant over ${opponent.hint()} (${opponent.why()})`); error = new Error(`T${this.transaction.id}[${this.transaction.hint}] is on hold/PreventWithError due to canceled T${opponent.transaction.id}[${opponent.transaction.hint}]`); this.transaction.cancel(error, opponent.transaction); break; case Reentrance.waitAndRestart: error = new Error(`T${this.transaction.id}[${this.transaction.hint}] is on hold/WaitAndRestart due to active T${opponent.transaction.id}[${opponent.transaction.hint}]`); this.transaction.cancel(error, opponent.transaction); break; case Reentrance.cancelAndWaitPrevious: error = new Error(`T${this.transaction.id}[${this.transaction.hint}] is on hold/CancelAndWaitPrevious due to active T${opponent.transaction.id}[${opponent.transaction.hint}]`); this.transaction.cancel(error, opponent.transaction); opponent.transaction.cancel(new Error(`T${opponent.transaction.id}[${opponent.transaction.hint}] is canceled due to re-entering T${this.transaction.id}[${this.transaction.hint}]`), null); break; case Reentrance.cancelPrevious: opponent.transaction.cancel(new Error(`T${opponent.transaction.id}[${opponent.transaction.hint}] is canceled due to re-entering T${this.transaction.id}[${this.transaction.hint}]`), null); break; case Reentrance.runSideBySide: break; } } if (!error) head.successor = this; else this.error = error; return this; } static proceed(footprint, proxy) { footprint.enter(); try { if (footprint.options.getter === undefined) console.log("(!)"); footprint.result = footprint.options.getter.call(proxy, ...footprint.args); } finally { footprint.leaveOrAsync(); } } enter() { if (this.options.indicator) this.indicatorEnter(this.options.indicator); if (Log.isOn && Log.opt.operation) Log.write("║", "‾\\", `${this.hint()} - enter`, undefined, ` [ ${Dump.obj(this.descriptor.ownerHandle, this.descriptor.fieldKey)} ]`); this.started = Date.now(); } leaveOrAsync() { if (this.result instanceof Promise) { this.result = this.result.then(value => { this.content = value; this.leave(false, " ⚐", "- finished ", " OK ──┘"); return value; }, error => { this.error = error; this.leave(false, " ⚐", "- finished ", "ERR ──┘"); throw error; }); if (Log.isOn) { if (Log.opt.operation) Log.write("║", "_/", `${this.hint()} - leave... `, 0, "ASYNC ──┐"); else if (Log.opt.transaction) Log.write("║", " ", `${this.why()} ...`, 0, "ASYNC"); } } else { this.content = this.result; this.leave(true, "_/", "- leave"); } } leave(main, op, message, highlight = undefined) { const ms = Date.now() - this.started; this.started = -this.started; if (Log.isOn && Log.opt.operation) Log.write("║", `${op}`, `${this.hint()} ${message}`, ms, highlight); if (ms > (main ? Mvcc.mainThreadBlockingWarningThreshold : Mvcc.asyncActionDurationWarningThreshold)) Log.write("", "[!]", this.why(), ms, main ? " *** main thread is too busy ***" : " *** async is too long ***"); this.cause = undefined; if (this.options.indicator) this.indicatorLeave(this.options.indicator); } indicatorEnter(mon) { const options = { hint: "Indicator.enter", isolation: Isolation.disjoinFromOuterAndInnerTransactions, logging: Log.isOn && Log.opt.indicator ? undefined : Log.global }; ReactionImpl.proceedWithinGivenLaunch(undefined, Transaction.run, options, IndicatorImpl.enter, mon, this.transaction); } indicatorLeave(mon) { Transaction.outside(() => { const leave = () => { const options = { hint: "Indicator.leave", isolation: Isolation.disjoinFromOuterAndInnerTransactions, logging: Log.isOn && Log.opt.indicator ? undefined : Log.DefaultLevel }; ReactionImpl.proceedWithinGivenLaunch(undefined, Transaction.run, options, IndicatorImpl.leave, mon, this.transaction); }; this.transaction.whenFinished().then(leave, leave); }); } addToDeferredReactiveFunctions() { OperationFootprintImpl.deferredReactions.push(this); if (OperationFootprintImpl.deferredReactions.length === 1) setTimeout(OperationFootprintImpl.processDeferredReactions, 0); } static processDeferredReactions() { const deferred = OperationFootprintImpl.deferredReactions; OperationFootprintImpl.deferredReactions = []; for (const x of deferred) x.relaunchIfNotUpToDate(true, true); } static markUsed(footprint, ov, fk, h, kind, weak) { if (kind !== Kind.transaction) { const listener = OperationFootprintImpl.current; if (listener && listener.options.kind !== Kind.transaction && listener.transaction === Transaction.current && fk !== Meta.Handle) { const ctx = Changeset.current(); if (ctx !== ov.changeset) ctx.bumpBy(ov.changeset.timestamp); const t = weak ? -1 : ctx.timestamp; if (!listener.listenTo(footprint, ov, fk, h, t)) listener.markObsoleteDueTo(footprint, fk, h.applied.changeset, h, BOOT_CAUSE, ctx.timestamp, ctx.obsolete); } } } static markEdited(oldValue, newValue, edited, ov, fk, h) { edited ? ov.changes.add(fk) : ov.changes.delete(fk); if (Log.isOn && Log.opt.write) edited ? Log.write("║", " =", `${Dump.snapshot2(h, ov.changeset, fk)} is changed: ${valueHint(oldValue)} ▸▸ ${valueHint(newValue)}`) : Log.write("║", " =", `${Dump.snapshot2(h, ov.changeset, fk)} is changed: ${valueHint(oldValue)} ▸▸ ${valueHint(newValue)}`, undefined, " (same as previous)"); } static tryResolveConflict(theirValue, ourFormerValue, ourValue) { let isResolved = theirValue === ourFormerValue; let resolvedValue = ourValue; if (!isResolved) { if (ourValue instanceof OperationFootprintImpl && ourValue.obsoleteDueTo === undefined) { isResolved = true; resolvedValue = ourValue; } else if (theirValue instanceof OperationFootprintImpl && (theirValue.obsoleteDueTo === undefined || theirValue.cause === BOOT_CAUSE)) { isResolved = true; resolvedValue = theirValue; } } return { isResolved, resolvedValue }; } static propagateAllChangesToListeners(changeset) { var _a; const since = changeset.timestamp; const obsolete = changeset.obsolete; changeset.items.forEach((ov, h) => { OperationFootprintImpl.propagateFieldChangeToListeners(false, since, ov, Meta.Revision, h, obsolete); if (!ov.disposed) ov.changes.forEach((o, fk) => OperationFootprintImpl.propagateFieldChangeToListeners(false, since, ov, fk, h, obsolete)); else for (const fk in ov.former.objectVersion.data) OperationFootprintImpl.propagateFieldChangeToListeners(true, since, ov, fk, h, obsolete); }); obsolete.sort(compareReactionsByOrder); (_a = changeset.options.journal) === null || _a === void 0 ? void 0 : _a.edited(JournalImpl.buildPatch(changeset.hint, changeset.items)); } static discardAllListeners(changeset) { changeset.items.forEach((ov, h) => { OperationFootprintImpl.propagateFieldChangeToListeners(true, changeset.timestamp, ov, Meta.Revision, h, undefined); ov.changes.forEach((o, fk) => OperationFootprintImpl.propagateFieldChangeToListeners(true, changeset.timestamp, ov, fk, h, undefined)); }); } static propagateFieldChangeToListeners(stopListening, timestamp, ov, fk, h, collector) { var _a; const curr = ov.data[fk]; if (collector !== undefined) { const former = ov.former.objectVersion.data[fk]; if (former !== undefined && former instanceof ContentFootprint) { const why = `T${ov.changeset.id}[${ov.changeset.hint}]`; if (former instanceof OperationFootprintImpl) { if ((former.obsoleteSince === MAX_REVISION || former.obsoleteSince <= 0)) { former.obsoleteDueTo = why; former.obsoleteSince = timestamp; former.stopListeningAllSignals(); } const formerSuccessor = former.successor; if (formerSuccessor !== curr) { if (formerSuccessor && !formerSuccessor.transaction.isFinished) formerSuccessor.transaction.cancel(new Error(`T${formerSuccessor.transaction.id}[${formerSuccessor.transaction.hint}] is canceled by T${ov.changeset.id}[${ov.changeset.hint}] and will not run anymore`), null); } else former.successor = undefined; } (_a = former.listeners) === null || _a === void 0 ? void 0 : _a.forEach(s => { const t = s.transaction; const o = t.isFinished ? collector : t.changeset.obsolete; return s.markObsoleteDueTo(former, fk, ov.changeset, h, why, timestamp, o); }); } } if (curr instanceof OperationFootprintImpl) { if (curr.changeset === ov.changeset && curr.signals !== undefined) { if (Mvcc.repetitiveUsageWarningThreshold < Number.MAX_SAFE_INTEGER) { curr.signals.forEach((info, v) => { if (info.usageCount > Mvcc.repetitiveUsageWarningThreshold) Log.write("", "[!]", `${curr.hint()} uses ${info.memberHint} ${info.usageCount} times (consider remembering it in a local variable)`, 0, " *** WARNING ***"); }); } if (stopListening) curr.stopListeningAllSignals(); } } else if (curr instanceof ContentFootprint && curr.listeners) { } } static enqueueReactionsToRun(reactions) { const queue = OperationFootprintImpl.queuedReactions; const isKickOff = queue.length === 0; for (const r of reactions) queue.push(r); if (isKickOff) ReactionImpl.proceedWithinGivenLaunch(undefined, OperationFootprintImpl.processQueuedReactions); } static migrateContentFootprint(cf, target) { let result; if (cf instanceof OperationFootprintImpl) result = new OperationFootprintImpl(target, cf.descriptor, target.changeset, cf, true); else result = new ContentFootprint(cf.content, cf.lastEditorChangesetId); return result; } static processQueuedReactions() { const queue = OperationFootprintImpl.queuedReactions; let i = 0; while (i < queue.length) { const reactive = queue[i]; reactive.relaunchIfNotUpToDate(false, true); i++; } OperationFootprintImpl.queuedReactions = []; } stopListeningAllSignals() { var _a; (_a = this.signals) === null || _a === void 0 ? void 0 : _a.forEach((info, value) => { var _a; value.listeners.delete(this); if (Log.isOn && (Log.opt.read || ((_a = this.options.logging) === null || _a === void 0 ? void 0 : _a.read))) Log.write(Log.opt.transaction && !Changeset.current().sealed ? "║" : " ", "-", `${this.hint()} is no longer listening to ${info.memberHint}`); }); this.signals = undefined; } listenTo(footprint, ov, fk, h, timestamp) { var _a, _b, _c; const parent = this.transaction.changeset.parent; const ok = OperationFootprintImpl.canListenTo(footprint, ov, parent, fk, h, timestamp); if (ok) { let times = 0; if (Mvcc.repetitiveUsageWarningThreshold < Number.MAX_SAFE_INTEGER) { const existing = this.signals.get(footprint); times = existing ? existing.usageCount + 1 : 1; } if (this.signals !== undefined) { if (!footprint.listeners) footprint.listeners = new Set(); const listening = { memberHint: Dump.snapshot2(h, ov.changeset, fk), usageCount: times }; footprint.listeners.add(this); this.signals.set(footprint, listening); if (Log.isOn && (Log.opt.read || ((_a = this.options.logging) === null || _a === void 0 ? void 0 : _a.read))) Log.write("║", " ∞", `${this.hint()} now listens to ${Dump.snapshot2(h, ov.changeset, fk, footprint)}${listening.usageCount > 1 ? ` (${listening.usageCount} times)` : ""}`); } else if (Log.isOn && (Log.opt.read || ((_b = this.options.logging) === null || _b === void 0 ? void 0 : _b.read))) Log.write("║", " x", `${this.hint()} is obsolete and is not listening to ${Dump.snapshot2(h, ov.changeset, fk, footprint)}`); } else { if (Log.isOn && (Log.opt.read || ((_c = this.options.logging) === null || _c === void 0 ? void 0 : _c.read))) Log.write("║", " x", `${this.hint()} is not listening to already obsolete ${Dump.snapshot2(h, ov.changeset, fk, footprint)}`); } return ok; } static canListenTo(footprint, ov, parent, fk, h, timestamp) { const parentSnapshot = parent ? parent.lookupObjectVersion(h, fk, false) : h.applied; const parentFootprint = parentSnapshot.data[fk]; let result = footprint === parentFootprint || (!ov.changeset.sealed && ov.former.objectVersion.data[fk] === parentFootprint); if (result && timestamp !== -1) result = !(footprint instanceof OperationFootprintImpl && timestamp >= footprint.obsoleteSince); return result; } static createOperationDescriptor(h, fk, options) { const ctl = new ReactionImpl(h, fk); const operation = (...args) => { return ctl.reuseOrRelaunch(false, args).result; }; Meta.set(operation, Meta.Descriptor, ctl); return operation; } static rememberOperationOptions(proto, fk, getter, setter, enumerable, configurable, options, implicit) { const initial = Meta.acquire(proto, Meta.Initial); let footprint = initial[fk]; const ctl = footprint ? footprint.descriptor : new ReactionImpl(EMPTY_HANDLE, fk); const opts = footprint ? footprint.options : OptionsImpl.INITIAL; initial[fk] = footprint = new OperationFootprintImpl(Transaction.current, ctl, EMPTY_OBJECT_VERSION.changeset, new OptionsImpl(getter, setter, opts, options, implicit), false); if (footprint.options.kind === Kind.reaction && footprint.options.throttling < Number.MAX_SAFE_INTEGER) { const reactive = Meta.acquire(proto, Meta.Reactive); reactive[fk] = footprint; } else if (footprint.options.kind === Kind.reaction && footprint.options.throttling >= Number.MAX_SAFE_INTEGER) { const reactive = Meta.getFrom(proto, Meta.Reactive); delete reactive[fk]; } return footprint.options; } static init() { Object.freeze(BOOT_ARGS); Log.getMergedLoggingOptions = getMergedLoggingOptions; Dump.valueHint = valueHint; Changeset.markUsed = OperationFootprintImpl.markUsed; Changeset.markEdited = OperationFootprintImpl.markEdited; Changeset.tryResolveConflict = OperationFootprintImpl.tryResolveConflict; Changeset.propagateAllChangesToListeners = OperationFootprintImpl.propagateAllChangesToListeners; Changeset.discardAllListeners = OperationFootprintImpl.discardAllListeners; Changeset.enqueueReactionsToRun = OperationFootprintImpl.enqueueReactionsToRun; TransactionImpl.migrateContentFootprint = OperationFootprintImpl.migrateContentFootprint; Mvcc.createOperationDescriptor = OperationFootprintImpl.createOperationDescriptor; Mvcc.rememberOperationOptions = OperationFootprintImpl.rememberOperationOptions; Promise.prototype.then = reactronicHookedThen; try { Object.defineProperty(globalThis, "rWhy", { get: ReactionImpl.why, configurable: false, enumerable: false, }); Object.defineProperty(globalThis, "rBriefWhy", { get: ReactionImpl.briefWhy, configurable: false, enumerable: false, }); } catch (e) { } try { Object.defineProperty(global, "rWhy", { get: ReactionImpl.why, configurable: false, enumerable: false, }); Object.defineProperty(global, "rBriefWhy", { get: ReactionImpl.briefWhy, configurable: false, enumerable: false, }); } catch (e) { } } } OperationFootprintImpl.current = undefined; OperationFootprintImpl.queuedReactions = []; OperationFootprintImpl.deferredReactions = []; function valueHint(value) { let result = ""; if (Array.isArray(value)) result = `Array(${value.length})`; else if (value instanceof Set) result = `Set(${value.size})`; else if (value instanceof Map) result = `Map(${value.size})`; else if (value instanceof OperationFootprintImpl) result = `#${value.descriptor.ownerHandle.id}t${value.changeset.id}s${value.changeset.timestamp}${value.lastEditorChangesetId !== undefined && value.lastEditorChangesetId !== 0 ? `t${value.lastEditorChangesetId}` : ""}`; else if (value === Meta.Undefined) result = "undefined"; else if (typeof (value) === "string") result = `"${value.toString().slice(0, 20)}${value.length > 20 ? "..." : ""}"`; else if (value !== undefined && value !== null) result = value.toString().slice(0, 40); else result = "undefined"; return result; } function getMergedLoggingOptions(local) { const t = Transaction.current; let res = Log.merge(t.options.logging, t.id > 1 ? 31 + t.id % 6 : 37, t.id > 1 ? `T${t.id}` : `-${Changeset.idGen.toString().replace(/[0-9]/g, "-")}`, Log.global); res = Log.merge({ margin1: t.margin }, undefined, undefined, res); if (OperationFootprintImpl.current) res = Log.merge({ margin2: OperationFootprintImpl.current.margin }, undefined, undefined, res); if (local) res = Log.merge(local, undefined, undefined, res); return res; } const ORIGINAL_PROMISE_THEN = Promise.prototype.then; function reactronicHookedThen(resolve, reject) { const tran = Transaction.current; if (!tran.isFinished) { if (!resolve) resolve = resolveReturn; if (!reject) reject = rejectRethrow; const footprint = OperationFootprintImpl.current; if (footprint) { resolve = footprint.wrap(resolve); reject = footprint.wrap(reject); } resolve = tran.wrapAsPending(resolve, false); reject = tran.wrapAsPending(reject, true); } return ORIGINAL_PROMISE_THEN.call(this, resolve, reject); } function compareReactionsByOrder(r1, r2) { return r1.order - r2.order; } export function resolveReturn(value) { return value; } export function rejectRethrow(error) { throw error; } OperationFootprintImpl.init();