import type { CompositeInternals, Description, Frame as IFrame, MutableInternals, ReactiveId, Timestamp, } from "@starbeam/interfaces"; import { REACTIVE, UNINITIALIZED } from "@starbeam/shared"; import { getID } from "./id.js"; import { ReactiveProtocol } from "./protocol.js"; import type { Timeline } from "./timeline.js"; import { getNow } from "./timestamp.js"; interface Marker { [REACTIVE]: Omit & { lastUpdated: Timestamp; }; } export class Frame implements ReactiveProtocol, IFrame { static create( this: void, value: T, children: Set, finalized: Timestamp, description: Description ): Frame { const id = getID(); return new Frame( id, value, { [REACTIVE]: { type: "mutable", lastUpdated: finalized, description: description.key("initialized?").asImplementation(), }, }, children, finalized, description ); } static uninitialized( finalized: Timestamp, description: Description ): Frame { const id = description.id; return new Frame( id, UNINITIALIZED, { [REACTIVE]: { type: "mutable", lastUpdated: finalized, description: description.detail("initialized?"), }, }, new Set(), finalized, description ); } static value(this: void, frame: Frame): T { return frame.#value; } static isInitialized(this: void, frame: Frame): boolean { return frame.#value !== UNINITIALIZED; } static update( this: void, frame: Frame, value: T, children: Set, finalized: Timestamp ): Frame { return frame.#update(value, children, finalized); } static updateChildren( this: void, frame: Frame, children: Set ): void { frame.#children = children; } #value: T; readonly #initialized: Marker; #children: ReadonlySet; #finalized: Timestamp; readonly #description: Description; constructor( id: ReactiveId, value: T, initialized: { [REACTIVE]: Omit & { lastUpdated: Timestamp; }; }, children: Set, finalized: Timestamp, description: Description ) { this.#value = value; this.#initialized = initialized; this.#children = children; this.#finalized = finalized; this.#description = description; } get [REACTIVE](): CompositeInternals { return { type: "composite", description: this.#description, children: () => { return [this.#initialized, ...this.#children]; }, }; } get description(): Description { return this.#description; } #update( this: Frame, value: U, children: Set, finalized: Timestamp ): Frame; #update( value: T, children: Set, finalized: Timestamp ): Frame; #update( value: T, children: Set, finalized: Timestamp ): this { if (Object.is(this.#value, UNINITIALIZED)) { this.#initialized[REACTIVE].lastUpdated = finalized; } this.#value = value; this.#children = children; this.#finalized = finalized; return this; } validate(): FrameValidation> { if ( this.#value === UNINITIALIZED || ReactiveProtocol.lastUpdatedIn([...this.#children]).gt(this.#finalized) ) { return { status: "invalid" }; } else { return { status: "valid", value: this.#value as Exclude, }; } } } export class ActiveFrame { static create( updating: Frame | null, prev: ActiveFrame | null, description: Description ): ActiveFrame { return new ActiveFrame(updating, prev, new Set(), description); } readonly #updating: Frame | null; readonly #prev: ActiveFrame | null; readonly #children: Set; private constructor( updating: Frame | null, prev: ActiveFrame | null, children: Set, readonly description: Description ) { this.#updating = updating; this.#prev = prev; this.#children = children; } add(child: ReactiveProtocol): void { this.#children.add(child); } finally(): { prev: ActiveFrame | null } { return { prev: this.#prev }; } finalize( value: T, timeline: Timeline ): { prev: ActiveFrame | null; frame: Frame } { let frame = this.#updating; if (frame) { Frame.update(frame, value, this.#children, getNow()); timeline.update(frame); } else { frame = Frame.create(value, this.#children, getNow(), this.description); } return { prev: this.#prev, frame }; } } export interface ValidFrame { readonly status: "valid"; readonly value: T; } export interface InvalidFrame { readonly status: "invalid"; } export type FrameValidation = ValidFrame | InvalidFrame;