/*! * Copyright (c) Microsoft Corporation and contributors. All rights reserved. * Licensed under the MIT License. */ import type { EventEmitterEventType } from "@fluid-internal/client-utils"; import type { IDeltaManager } from "@fluidframework/container-definitions/internal"; import type { ErasedType } from "@fluidframework/core-interfaces"; import type { IFluidHandleInternal, IFluidLoadable } from "@fluidframework/core-interfaces/internal"; import type { IChannelServices, IChannelStorageService, IChannelAttributes, IChannelFactory, IFluidDataStoreRuntime } from "@fluidframework/datastore-definitions/internal"; import type { IDocumentMessage, ISequencedDocumentMessage, RegistryKey } from "@fluidframework/driver-definitions/internal"; import { type IExperimentalIncrementalSummaryContext, type ISummaryTreeWithStats, type ITelemetryContext, type IGarbageCollectionData, type IRuntimeMessageCollection } from "@fluidframework/runtime-definitions/internal"; import { EventEmitterWithErrorHandling } from "@fluidframework/telemetry-utils/internal"; import type { ITelemetryLoggerExt } from "@fluidframework/telemetry-utils/legacy"; import { type IFluidSerializer } from "./serializer.js"; import type { ISharedObject, ISharedObjectEvents } from "./types.js"; /** * Base class from which all {@link ISharedObject|shared objects} derive. * @remarks * This class implements common behaviors that implementations of {@link ISharedObject} may want to reuse. * Even more such behaviors are implemented in the {@link SharedObject} class. * @privateRemarks * Currently some documentation (like the above) implies that this is supposed to be the only implementation of ISharedObject, which is both package-exported and not `@sealed`. * This situation should be clarified to indicate if other implementations of ISharedObject are allowed and just currently don't exist, * or if the intention is that no other implementations should exist and creating some might break things. * As part of this, any existing implementations of ISharedObject (via SharedObjectCore or otherwise) in use by legacy API users will need to be considered. * * TODO: * This class should eventually be made internal, as custom subclasses of it outside this repository are intended to be made unsupported in the future. * @legacy @beta */ export declare abstract class SharedObjectCore extends EventEmitterWithErrorHandling implements ISharedObject { #private; /** * The ID of the shared object. */ id: string; /** * The runtime instance that contains the Shared Object. */ protected runtime: IFluidDataStoreRuntime; /** * The attributes of the Shared Object. */ readonly attributes: IChannelAttributes; get IFluidLoadable(): this; private readonly opProcessingHelper; private readonly callbacksHelper; /** * The handle referring to this SharedObject */ readonly handle: IFluidHandleInternal; /** * Telemetry logger for the shared object */ protected readonly logger: ITelemetryLoggerExt; private readonly mc; /** * Connection state */ private _connected; /** * Services used by the shared object */ private services; /** * True if the dds is bound to its parent. */ private _isBoundToContext; /** * Tracks error that closed this object. */ private closeError?; /** * Gets the connection state * @returns The state of the connection */ get connected(): boolean; constructor( /** * The ID of the shared object. */ id: string, /** * The runtime instance that contains the Shared Object. */ runtime: IFluidDataStoreRuntime, /** * The attributes of the Shared Object. */ attributes: IChannelAttributes); /** * Accessor for `this.runtime`'s {@link @fluidframework/datastore-definitions#IFluidDataStoreRuntime.deltaManager} as a {@link @fluidframework/container-definitions/internal#IDeltaManager} */ protected get deltaManager(): IDeltaManager; /** * This function is only supposed to be called from SharedObjectCore's constructor and * depends on a few things being set already. assert() calls make sure of it. * @returns The telemetry sampling helpers, so the constructor can be the one to assign them * to variables to avoid complaints from TypeScript. */ private setUpSampledTelemetryHelpers; /** * Marks this objects as closed. Any attempt to change it (local changes or processing remote ops) * would result in same error thrown. If called multiple times, only first error is remembered. * @param error - error object that is thrown whenever an attempt is made to modify this object */ private closeWithError; /** * Verifies that this object is not closed via closeWithError(). If it is, throws an error used to close it. */ private verifyNotClosed; /** * Event listener handler helper that can be used to react to exceptions thrown from event listeners * It wraps error with DataProcessingError, closes this object and throws resulting error. * See closeWithError() for more details * Ideally such situation never happens, as consumers of DDS should never throw exceptions * in event listeners (i.e. catch any of the issues and make determination on how to handle it). * When such exceptions propagate through, most likely data model is no longer consistent, i.e. * DDS state does not match what user sees. Because of it DDS moves to "corrupted state" and does not * allow processing of ops or local changes, which very quickly results in container closure. */ private eventListenerErrorHandler; private setBoundAndHandleAttach; /** * A shared object, after construction, can either be loaded in the case that it is already part of * a shared document. Or later attached if it is being newly added. * @param services - Services used by the shared object */ load(services: IChannelServices): Promise; /** * Initializes the object as a local, non-shared object. This object can become shared after * it is attached to the document. */ initializeLocal(): void; bindToContext(): void; connect(services: IChannelServices): void; isAttached(): boolean; abstract getAttachSummary(fullTree?: boolean, trackState?: boolean, telemetryContext?: ITelemetryContext): ISummaryTreeWithStats; abstract summarize(fullTree?: boolean, trackState?: boolean, telemetryContext?: ITelemetryContext): Promise; abstract getGCData(fullGC?: boolean): IGarbageCollectionData; /** * Allows the distributed data type to perform custom loading * @param services - Storage used by the shared object */ protected abstract loadCore(services: IChannelStorageService): Promise; /** * Allows the distributed data type to perform custom local loading. */ protected initializeLocalCore(): void; /** * Allows the distributive data type the ability to perform custom processing once an attach has happened. * Also called after non-local data type get loaded. */ protected didAttach(): void; /** * Apply a 'bunch' of sequenced ops to this shared object. * @remarks * See {@link @fluidframework/runtime-definitions#IRuntimeMessageCollection} for what a "bunch" is. * * These ops have been sequenced by the service and now have a finalized ordering. * They may be local or remote ops, but they cannot be ops that are still pending acknowledgement from the service. * * @param messageCollection - The 'bunch' of sequenced ops to apply to this shared object. * @privateRemarks * TODO:Performance: AB#59783: Allowing this to process more messages at once (more than the current definition of 'bunch') could improve performance of clients which fall behind, * which is one of the most important performance sensitive scenarios. */ protected abstract processMessagesCore(messagesCollection: IRuntimeMessageCollection): void; /** * Called when the object has disconnected from the delta stream. */ protected abstract onDisconnect(): void; /** * The serializer to serialize / parse handles. */ protected abstract get serializer(): IFluidSerializer; /** * Submits a message by the local client to the runtime. * @param content - Content of the message. Note: handles contained in the * message object should not be encoded in any way * @param localOpMetadata - The local metadata associated with the message. This is kept locally by the runtime * and not sent to the server. This will be sent back when this message is received back from the server. This is * also sent if we are asked to resubmit the message. */ protected submitLocalMessage(content: unknown, localOpMetadata?: unknown): void; /** * Marks this object as dirty so that it is part of the next summary. It is called by a SharedSummaryBlock * that want to be part of summary but does not generate ops. */ protected dirty(): void; /** * Called when the object has fully connected to the delta stream * Default implementation for DDS, override if different behavior is required. */ protected onConnect(): void; /** * Called when a message has to be resubmitted. This typically happens after a reconnection for unacked messages. * The default implementation here is to resubmit the same message. The client can override if different behavior * is required. It can choose to resubmit the same message, submit different / multiple messages or not submit * anything at all. * @param content - The content of the original message. * @param localOpMetadata - The local metadata associated with the original message. */ protected reSubmitCore(content: unknown, localOpMetadata: unknown): void; /** * Called when a message has to be resubmitted but its content should be "squashed" if any subsequent pending changes * override the content in the fashion described on {@link @fluidframework/datastore-definitions#IDeltaHandler.reSubmit}. * * @param content - The content of the original message. * @param localOpMetadata - The local metadata associated with the original message. */ protected reSubmitSquashed(content: unknown, localOpMetadata: unknown): void; /** * Promises that are waiting for an ack from the server before resolving should use this instead of new Promise. * It ensures that if something changes that will interrupt that ack (e.g. the FluidDataStoreRuntime disposes), * the Promise will reject. * If runtime is disposed when this call is made, executor is not run and promise is rejected right away. */ protected newAckBasedPromise(executor: (resolve: (value: T | PromiseLike) => void, reject: (reason?: unknown) => void) => void): Promise; private attachDeltaHandler; /** * Set the state of connection to services. * @param connected - true if connected, false otherwise. */ private setConnectionState; /** * Process a bunch of messages for this shared object. A bunch is group of messages that have the following properties: * - They are all part of the same grouped batch, which entails: * - They are contiguous in sequencing order. * - They are all from the same client. * - They are all based on the same reference sequence number. * - They are not interleaved with messages from other clients. * - They are not interleaved with messages from other DDS in the container. * @param messageCollection - The collection of messages to process. * */ private processMessages; /** * Called when a message has to be resubmitted. This typically happens for unacked messages after a * reconnection. * @param content - The content of the original message. * @param localOpMetadata - The local metadata associated with the original message. * @param squash - If `true`, the message will be resubmitted in a squashed form. If `false`, * the legacy behavior (no squashing) will be used. */ private reSubmit; /** * Revert an op */ protected rollback(content: unknown, localOpMetadata: unknown): void; /** * Apply changes from the provided op content just as if a local client has made the change, * including submitting the op. Used when rehydrating an attached container * with pending changes. The rehydration process replays all remote ops * and applies stashed ops after the remote op with a sequence number * that matches that of the stashed op is applied. This ensures * stashed ops are applied at the same state they were originally created. * * It is possible that stashed ops have been sent in the past, and will be found when * the shared object catches up with remotes ops. * So this prepares the SharedObject for seeing potentially seeing the ACK. * If no matching remote op is found, all the applied stashed ops will go * through the normal resubmit flow upon reconnection, which allows the dds * to rebase them to the latest state, and then resubmit them. * * @param content - Contents of a stashed op. */ protected abstract applyStashedOp(content: unknown): void; /** * Emit an event. This function is only intended for use by DDS classes that extend SharedObject/SharedObjectCore, * specifically to emit events that are part of the public interface of the DDS (i.e. those that can have listeners * attached to them by the consumers of the DDS). It should not be called from outside the class or to emit events * which are only internal to the DDS. Support for calling it from outside the DDS instance might be removed in the * future. * @param event - The event to emit. * @param args - Arguments to pass to the event listeners. * @returns `true` if the event had listeners, `false` otherwise. */ emit(event: EventEmitterEventType, ...args: any[]): boolean; /** * Use to emit events inside {@link SharedObjectCore}, with no telemetry measurement * done on the duration of the callbacks. Simply calls `super.emit()`. * @param event - Event to emit * @param args - Arguments for the event * @returns Whatever `super.emit()` returns. */ private emitInternal; private throwUnsupported; } /** * Helper for implementing {@link ISharedObject} with simplified, synchronous summarization and garbage collection. * @remarks * DDS implementations with async and incremental summarization should extend {@link SharedObjectCore} directly instead. * @privateRemarks * TODO: * This class is badly named. * Once it becomes `@internal` "SharedObjectCore" should probably become "SharedObject" * and this class should be renamed to something like "SharedObjectSynchronous". * @legacy @beta */ export declare abstract class SharedObject extends SharedObjectCore { /** * The prefix to use for telemetry events emitted by this object. */ private readonly telemetryContextPrefix; /** * True while we are garbage collecting this object's data. */ private _isGCing; /** * The serializer to use to serialize / parse handles, if any. */ private readonly _serializer; protected get serializer(): IFluidSerializer; constructor(id: string, runtime: IFluidDataStoreRuntime, attributes: IChannelAttributes, /** * The prefix to use for telemetry events emitted by this object. */ telemetryContextPrefix: string); getAttachSummary(fullTree?: boolean, trackState?: boolean, telemetryContext?: ITelemetryContext): ISummaryTreeWithStats; summarize(fullTree?: boolean, trackState?: boolean, telemetryContext?: ITelemetryContext, incrementalSummaryContext?: IExperimentalIncrementalSummaryContext): Promise; getGCData(fullGC?: boolean): IGarbageCollectionData; /** * Calls the serializer over all data in this object that reference other GC nodes. * Derived classes must override this to provide custom list of references to other GC nodes. * * @remarks Serialization itself doesn't matter (the result is ignored). We're tapping into the serialization infrastructure * as a way to visit all the content in this content that may reference other objects via handle. * * @param serializer - The "serializer" (more like handle visitor) to use. * Implementations should ensure that serialize is called on all handles, as the way to visit them. */ protected processGCDataCore(serializer: IFluidSerializer): void; /** * Gets a form of the object that can be serialized. * @returns A tree representing the snapshot of the shared object. */ protected abstract summarizeCore(serializer: IFluidSerializer, telemetryContext?: ITelemetryContext, incrementalSummaryContext?: IExperimentalIncrementalSummaryContext, fullTree?: boolean): ISummaryTreeWithStats; private incrementTelemetryMetric; } /** * Defines a kind of shared object. * Used in containers to register a shared object implementation, and to create new instances of a given type of shared object. * * @remarks * For use internally and in the "encapsulated API". * See {@link SharedObjectKind} for the type erased version for use in the public declarative API. * * @privateRemarks * This does not extend {@link SharedObjectKind} since doing so would prevent implementing this interface in type safe code. * Any implementation of this can safely be used as a {@link SharedObjectKind} with an explicit type conversion, * but doing so is typically not needed as {@link createSharedObjectKind} is used to produce values that are both types simultaneously. * @legacy @beta */ export interface ISharedObjectKind { /** * Get a factory which can be used by the Fluid Framework to programmatically instantiate shared objects within containers. * @remarks * The produced factory is intended for use with the FluidDataStoreRegistry and is used by the Fluid Framework to instantiate already existing Channels. * To create new shared objects use: * * - {@link @fluidframework/fluid-static#IFluidContainer.create} if using `@fluidframework/fluid-static`, for example via `@fluidframework/azure-client`. * * - {@link ISharedObjectKind.create} if using a custom container definitions (and thus not using {@link @fluidframework/fluid-static#IFluidContainer}). */ getFactory(): IChannelFactory; /** * Create a shared object. * @param runtime - The data store runtime that the new shared object belongs to. * @param id - Optional name of the shared object. * @returns Newly created shared object. * * @example * To create a `SharedTree`, call the static create method: * * ```typescript * const myTree = SharedTree.create(this.runtime, id); * ``` * @remarks * The created object is local (detached): insert a handle to it into an attached object to share (attach) it. * If using `@fluidframework/fluid-static` (for example via `@fluidframework/azure-client`), use {@link @fluidframework/fluid-static#IFluidContainer.create} instead of calling this directly. * * @privateRemarks * This can only be used with a `MockFluidDataStoreRuntime` when that mock is created with a `registry` containing a factory for this shared object. */ create(runtime: IFluidDataStoreRuntime, id?: string): TSharedObject; } /** * Defines a kind of shared object. * @remarks * Used in containers to register a shared object implementation, and to create new instances of a given type of shared object. * See {@link @fluidframework/fluid-static#IFluidContainer.create} and {@link @fluidframework/fluid-static#ContainerSchema} for details. * @privateRemarks * Part of the "declarative API". * Type erased reference to an {@link ISharedObjectKind} or a DataObject class in for use in * `fluid-static`'s `IFluidContainer` and `ContainerSchema`. * Use {@link createSharedObjectKind} to creating an instance of this type. * @sealed * @public */ export interface SharedObjectKind extends ErasedType { /** * Check whether an {@link @fluidframework/core-interfaces#IFluidLoadable} is an instance of this shared object kind. * @remarks This should be used in place of `instanceof` checks for shared objects, as their actual classes are not exported in Fluid's public API. */ is(value: IFluidLoadable): value is IFluidLoadable & TSharedObject; } /** * A {@link @fluidframework/driver-definitions#RegistryKey} for a {@link SharedObjectKind}. * @remarks * This is implemented by {@link SharedObjectKind}, but alternative implementations can be used if needed. * * If you want lazy loading and need a key that does not eagerly load the {@link SharedObjectKind}, an alternative {@link SharedObjectKey} can be implemented. * @privateRemarks * TODO: A built in common pattern for the lazy key case should be provided. * TODO: things probably break if "adapt" does anything except throw or return the result from the input promise. * @input * @alpha */ export type SharedObjectKey = RegistryKey, SharedObjectKindAlpha>; /** * A {@link SharedObjectKind} that also implements {@link SharedObjectKey}. * @privateRemarks * All concrete shared object kinds (produced by {@link createSharedObjectKind}) implement this interface. * @sealed * @alpha */ export interface SharedObjectKindAlpha extends SharedObjectKind, SharedObjectKey { } /** * Utility for creating ISharedObjectKind instances. * @remarks * This takes in a class which implements IChannelFactory, * and uses it to return a a single value which is intended to be used as the API entry point for the corresponding shared object type. * The returned value implements {@link ISharedObjectKind} for use in the encapsulated API, as well as the type erased {@link SharedObjectKind} used by the declarative API. * See {@link @fluidframework/fluid-static#ContainerSchema} for how this is used in the declarative API. * @internal */ export declare function createSharedObjectKind(factory: (new () => IChannelFactory) & { readonly Type: string; }): ISharedObjectKind & SharedObjectKind; /** * Utility for creating ISharedObjectKind instances. * @remarks * This takes in a class which implements IChannelFactory, * and uses it to return a single value which is intended to be used as the API entry point for the corresponding shared object type. * The returned value implements {@link ISharedObjectKind} for use in the encapsulated API, as well as the type erased {@link SharedObjectKind} used by the declarative API. * See {@link @fluidframework/fluid-static#ContainerSchema} for how this is used in the declarative API. * @internal */ export declare function createSharedObjectKindAlpha(factory: (new () => IChannelFactory) & { readonly Type: string; }): ISharedObjectKind & SharedObjectKindAlpha; //# sourceMappingURL=sharedObject.d.ts.map