/*! * Copyright (c) Microsoft Corporation and contributors. All rights reserved. * Licensed under the MIT License. */ import { TypedEventEmitter } from "@fluid-internal/client-utils"; import type { IChannelAttributes, IFluidDataStoreRuntime, IChannelStorageService } from "@fluidframework/datastore-definitions/internal"; import type { ISummaryTreeWithStats, ITelemetryContext, IRuntimeMessageCollection, ISequencedMessageEnvelope } from "@fluidframework/runtime-definitions/internal"; import type { IFluidSerializer } from "@fluidframework/shared-object-base/internal"; import { SharedObject } from "@fluidframework/shared-object-base/internal"; import { type TelemetryLoggerExt } from "@fluidframework/telemetry-utils/internal"; import type { IDirectory, IDirectoryEvents, ISharedDirectory, ISharedDirectoryEvents } from "./interfaces.js"; import type { ISerializableValue, ISerializedValue } from "./internalInterfaces.js"; /** * Operation indicating a value should be set for a key. */ export interface IDirectorySetOperation { /** * String identifier of the operation type. */ type: "set"; /** * Directory key being modified. */ key: string; /** * Absolute path of the directory where the modified key is located. */ path: string; /** * Value to be set on the key. */ value: ISerializableValue; } /** * Operation indicating a key should be deleted from the directory. */ export interface IDirectoryDeleteOperation { /** * String identifier of the operation type. */ type: "delete"; /** * Directory key being modified. */ key: string; /** * Absolute path of the directory where the modified key is located. */ path: string; } /** * An operation on a specific key within a directory. */ export type IDirectoryKeyOperation = IDirectorySetOperation | IDirectoryDeleteOperation; /** * Operation indicating the directory should be cleared. */ export interface IDirectoryClearOperation { /** * String identifier of the operation type. */ type: "clear"; /** * Absolute path of the directory being cleared. */ path: string; } /** * An operation on one or more of the keys within a directory. */ export type IDirectoryStorageOperation = IDirectoryKeyOperation | IDirectoryClearOperation; /** * Operation indicating a subdirectory should be created. */ export interface IDirectoryCreateSubDirectoryOperation { /** * String identifier of the operation type. */ type: "createSubDirectory"; /** * Absolute path of the directory that will contain the new subdirectory. */ path: string; /** * Name of the new subdirectory. */ subdirName: string; } /** * Operation indicating a subdirectory should be deleted. */ export interface IDirectoryDeleteSubDirectoryOperation { /** * String identifier of the operation type. */ type: "deleteSubDirectory"; /** * Absolute path of the directory that contains the directory to be deleted. */ path: string; /** * Name of the subdirectory to be deleted. */ subdirName: string; } /** * An operation on the subdirectories within a directory. */ export type IDirectorySubDirectoryOperation = IDirectoryCreateSubDirectoryOperation | IDirectoryDeleteSubDirectoryOperation; /** * Any operation on a directory. */ export type IDirectoryOperation = IDirectoryStorageOperation | IDirectorySubDirectoryOperation; interface PendingKeySet { type: "set"; path: string; value: unknown; subdir: SubDirectory; } interface PendingKeyDelete { type: "delete"; path: string; key: string; subdir: SubDirectory; } interface PendingClear { type: "clear"; path: string; subdir: SubDirectory; } /** * Create info for the subdirectory. * * @deprecated This interface will no longer be exported in the future(AB#8004). * * @legacy @beta */ export interface ICreateInfo { /** * Sequence number at which this subdirectory was created. */ csn: number; /** * clientids of the clients which created this sub directory. */ ccIds: string[]; } /** * Defines the in-memory object structure to be used for the conversion to/from serialized. * * @remarks Directly used in * {@link https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/JSON/stringify * | JSON.stringify}, direct result from * {@link https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/JSON/parse | JSON.parse}. * * @deprecated This interface will no longer be exported in the future(AB#8004). * * @legacy @beta */ export interface IDirectoryDataObject { /** * Key/value date set by the user. */ storage?: Record; /** * Recursive sub-directories {@link IDirectoryDataObject | objects}. */ subdirectories?: Record; /** * Create info for the sub directory. Since directories with same name can get deleted/created by multiple clients * asynchronously, this info helps us to determine whether the ops where for the current instance of sub directory * or not and whether to process them or not based on that. Summaries which were not produced which this change * will not have this info and in that case we can still run in eventual consistency issues but that is no worse * than the state before this change. */ ci?: ICreateInfo; } /** * {@link IDirectory} storage format. * * @deprecated This interface will no longer be exported in the future(AB#8004). * * @legacy @beta */ export interface IDirectoryNewStorageFormat { /** * Blob IDs representing larger directory data that was serialized. */ blobs: string[]; /** * Storage content representing directory data that was not serialized. */ content: IDirectoryDataObject; } /** * The combination of sequence numebr and client sequence number of a subdirectory */ interface SequenceData { seq: number; clientSeq?: number; } /** * {@inheritDoc ISharedDirectory} * * @example * * ```typescript * mySharedDirectory.createSubDirectory("a").createSubDirectory("b").createSubDirectory("c").set("foo", val1); * const mySubDir = mySharedDirectory.getWorkingDirectory("/a/b/c"); * mySubDir.get("foo"); // returns val1 * ``` * * @sealed */ export declare class SharedDirectory extends SharedObject implements ISharedDirectory { /** * String representation for the class. */ [Symbol.toStringTag]: string; /** * {@inheritDoc IDirectory.absolutePath} */ get absolutePath(): string; /** * Root of the SharedDirectory, most operations on the SharedDirectory itself act on the root. */ private readonly root; /** * Mapping of op types to message handlers. */ private readonly messageHandlers; /** * Constructs a new shared directory. If the object is non-local an id and service interfaces will * be provided. * @param id - String identifier for the SharedDirectory * @param runtime - Data store runtime * @param type - Type identifier */ constructor(id: string, runtime: IFluidDataStoreRuntime, attributes: IChannelAttributes); /** * {@inheritDoc IDirectory.get} */ get(key: string): T | undefined; /** * {@inheritDoc IDirectory.set} */ set(key: string, value: T): this; dispose(error?: Error): void; get disposed(): boolean; /** * Deletes the given key from within this IDirectory. * @param key - The key to delete * @returns True if the key existed and was deleted, false if it did not exist */ delete(key: string): boolean; /** * Deletes all keys from within this IDirectory. */ clear(): void; /** * Checks whether the given key exists in this IDirectory. * @param key - The key to check * @returns True if the key exists, false otherwise */ has(key: string): boolean; /** * The number of entries under this IDirectory. */ get size(): number; /** * Issue a callback on each entry under this IDirectory. * @param callback - Callback to issue */ forEach(callback: (value: any, key: string, map: Map) => void): void; /** * Get an iterator over the entries under this IDirectory. * @returns The iterator */ [Symbol.iterator](): IterableIterator<[string, any]>; /** * Get an iterator over the entries under this IDirectory. * @returns The iterator */ entries(): IterableIterator<[string, any]>; /** * {@inheritDoc IDirectory.countSubDirectory} */ countSubDirectory(): number; /** * Get an iterator over the keys under this IDirectory. * @returns The iterator */ keys(): IterableIterator; /** * Get an iterator over the values under this IDirectory. * @returns The iterator */ values(): IterableIterator; /** * {@inheritDoc IDirectory.createSubDirectory} */ createSubDirectory(subdirName: string): IDirectory; /** * {@inheritDoc IDirectory.getSubDirectory} */ getSubDirectory(subdirName: string): IDirectory | undefined; /** * {@inheritDoc IDirectory.hasSubDirectory} */ hasSubDirectory(subdirName: string): boolean; /** * {@inheritDoc IDirectory.deleteSubDirectory} */ deleteSubDirectory(subdirName: string): boolean; /** * {@inheritDoc IDirectory.subdirectories} */ subdirectories(): IterableIterator<[string, IDirectory]>; /** * {@inheritDoc IDirectory.getWorkingDirectory} */ getWorkingDirectory(relativePath: string): IDirectory | undefined; /** * Similar to `getWorkingDirectory`, but only returns directories that are sequenced. * This can be useful for op processing since we only process ops on sequenced directories. */ private getSequencedWorkingDirectory; /** * {@inheritDoc @fluidframework/shared-object-base#SharedObject.summarizeCore} */ protected summarizeCore(serializer: IFluidSerializer, telemetryContext?: ITelemetryContext): ISummaryTreeWithStats; /** * Submits an operation * @param op - Op to submit * @param localOpMetadata - The local metadata associated with the op. We send a unique id that is used to track * this op while it has not been ack'd. This will be sent when we receive this op back from the server. */ submitDirectoryMessage(op: IDirectoryOperation, localOpMetadata: DirectoryLocalOpMetadata): void; /** * {@inheritDoc @fluidframework/shared-object-base#SharedObject.onDisconnect} */ protected onDisconnect(): void; /** * {@inheritDoc @fluidframework/shared-object-base#SharedObject.reSubmitCore} */ protected reSubmitCore(content: unknown, localOpMetadata: DirectoryLocalOpMetadata): void; /** * {@inheritDoc @fluidframework/shared-object-base#SharedObject.loadCore} */ protected loadCore(storage: IChannelStorageService): Promise; /** * Populate the directory with the given directory data. * @param data - A JSON string containing serialized directory data */ protected populate(data: IDirectoryDataObject): void; protected processMessagesCore(messagesCollection: IRuntimeMessageCollection): void; private processMessage; /** * {@inheritDoc @fluidframework/shared-object-base#SharedObject.rollback} */ protected rollback(content: unknown, localOpMetadata: DirectoryLocalOpMetadata): void; /** * Converts the given relative path to absolute against the root. * @param relativePath - The path to convert */ private makeAbsolute; /** * Set the message handlers for the directory. */ private setMessageHandlers; /** * {@inheritDoc @fluidframework/shared-object-base#SharedObjectCore.applyStashedOp} */ protected applyStashedOp(op: unknown): void; private serializeDirectory; } interface ICreateSubDirLocalOpMetadata { type: "createSubDir"; parentSubdir: SubDirectory; } interface IDeleteSubDirLocalOpMetadata { type: "deleteSubDir"; subDirectory: SubDirectory | undefined; parentSubdir: SubDirectory; } type SubDirLocalOpMetadata = ICreateSubDirLocalOpMetadata | IDeleteSubDirLocalOpMetadata; type EditLocalOpMetadata = PendingKeySet | PendingKeyDelete; type ClearLocalOpMetadata = PendingClear; type StorageLocalOpMetadata = EditLocalOpMetadata | ClearLocalOpMetadata; /** * Types of local op metadata. */ export type DirectoryLocalOpMetadata = StorageLocalOpMetadata | SubDirLocalOpMetadata; /** * Node of the directory tree. * @sealed */ declare class SubDirectory extends TypedEventEmitter implements IDirectory { private readonly seqData; private readonly clientIds; private readonly directory; private readonly runtime; private readonly serializer; readonly absolutePath: string; /** * Tells if the sub directory is deleted or not. */ private _deleted; /** * String representation for the class. */ [Symbol.toStringTag]: string; /** * The sequenced subdirectories the directory is holding independent of any pending * create/delete subdirectory operations. */ private readonly _sequencedSubdirectories; /** * Assigns a unique ID to each subdirectory created locally but pending for acknowledgement, facilitating the tracking * of the creation order. */ localCreationSeq: number; private readonly mc; /** * Constructor. * @param sequenceNumber - Message seq number at which this was created. * @param clientIds - Ids of client which created this directory. * @param directory - Reference back to the SharedDirectory to perform operations * @param runtime - The data store runtime this directory is associated with * @param serializer - The serializer to serialize / parse handles * @param absolutePath - The absolute path of this IDirectory */ constructor(seqData: SequenceData, clientIds: Set, directory: SharedDirectory, runtime: IFluidDataStoreRuntime, serializer: IFluidSerializer, absolutePath: string, logger: TelemetryLoggerExt); dispose(error?: Error): void; /** * Unmark the deleted property only when rolling back delete. */ private undispose; get disposed(): boolean; private throwIfDisposed; /** * Checks whether the given key exists in this IDirectory. * @param key - The key to check * @returns True if the key exists, false otherwise */ has(key: string): boolean; /** * {@inheritDoc IDirectory.get} */ get(key: string): T | undefined; /** * {@inheritDoc IDirectory.set} */ set(key: string, value: T): this; /** * {@inheritDoc IDirectory.countSubDirectory} */ countSubDirectory(): number; /** * {@inheritDoc IDirectory.createSubDirectory} */ createSubDirectory(subdirName: string): IDirectory; /** * Gets the Sequence Data which should be used for local changes. * * @remarks While detached, 0 is used rather than -1 to represent a change which should be universally known (as opposed to known * only by the local client). This ensures that if the directory is later attached, none of its data needs to be updated (the values * last set while detached will now be known to any new client, until they are changed). * * The client sequence number is incremented by 1 for maintaining the internal order of locally created subdirectories * * @privateRemarks TODO: Convert these conventions to named constants. The semantics used here match those for merge-tree. */ private getLocalSeq; /** * {@inheritDoc IDirectory.getSubDirectory} */ getSubDirectory(subdirName: string): IDirectory | undefined; /** * {@inheritDoc IDirectory.hasSubDirectory} */ hasSubDirectory(subdirName: string): boolean; /** * {@inheritDoc IDirectory.deleteSubDirectory} */ deleteSubDirectory(subdirName: string): boolean; /** * {@inheritDoc IDirectory.subdirectories} */ subdirectories(): IterableIterator<[string, IDirectory]>; /** * {@inheritDoc IDirectory.getWorkingDirectory} */ getWorkingDirectory(relativePath: string): IDirectory | undefined; /** * This checks if there is pending delete op for local delete for a given child subdirectory. * @param subDirName - directory name. * @returns true if there is pending delete. */ isSubDirectoryDeletePending(subDirName: string): boolean; /** * Deletes the given key from within this IDirectory. * @param key - The key to delete * @returns True if the key existed and was deleted, false if it did not exist */ delete(key: string): boolean; /** * Deletes all keys from within this IDirectory. */ clear(): void; /** * Issue a callback on each entry under this IDirectory. * @param callback - Callback to issue */ forEach(callback: (value: unknown, key: string, map: Map) => void): void; /** * The number of entries under this IDirectory. */ get size(): number; /** * Get an iterator over the entries under this IDirectory. * @returns The iterator */ entries(): IterableIterator<[string, unknown]>; /** * Get an iterator over the keys under this IDirectory. * @returns The iterator */ keys(): IterableIterator; /** * Get an iterator over the values under this IDirectory. * @returns The iterator */ values(): IterableIterator; /** * Get an iterator over the entries under this IDirectory. * @returns The iterator */ [Symbol.iterator](): IterableIterator<[string, unknown]>; /** * The data this SubDirectory instance is storing, but only including sequenced values (no local pending * modifications are included). */ private readonly sequencedStorageData; /** * A data structure containing all local pending storage modifications, which is used in combination * with the sequencedStorageData to compute optimistic values. * * Pending sets are aggregated into "lifetimes", which permit correct relative iteration order * even across remote operations and rollbacks. */ private readonly pendingStorageData; /** * A data structure containing all local pending subdirectory create/deletes, which is used in combination * with the _sequencedSubdirectories to compute optimistic values. */ private readonly pendingSubDirectoryData; /** * An internal iterator that iterates over the entries in the directory. */ private readonly internalIterator; /** * Compute the optimistic local value for a given key. This combines the sequenced data with * any pending changes that have not yet been sequenced. */ private readonly getOptimisticValue; /** * Determine if the directory optimistically has the key. * This will return true even if the value is undefined. */ private readonly optimisticallyHas; /** * Get the optimistic local subdirectory. This combines the sequenced data with * any pending changes that have not yet been sequenced. By default, we do not * consider disposed directories as optimistically existing, but if `getIfDisposed` * is true, we will include them since some scenarios require this. */ private readonly getOptimisticSubDirectory; /** * Checks if this directory should be considered visible in the optimistic view. * This requires both: * 1. The directory object must not be disposed (disposed = true means fully deleted) * 2. The directory must be reachable via getWorkingDirectory (respects pending deletes) * * There's a timing window where a directory has a pending delete but is not yet disposed: * - When deleteSubDirectory is called locally, it adds a pending delete entry and emits a * "dispose" event, but doesn't set disposed = true yet. * - The directory only gets fully disposed when the delete message is sequenced. * - During this window, !disposed is true but getWorkingDirectory returns undefined. * * This method should be used before emitting events during remote message processing to ensure * events aren't emitted for directories that are invisible in the optimistic view. * * @returns true if this directory is visible in the optimistic view, false otherwise */ private isNotDisposedAndReachable; get sequencedSubdirectories(): ReadonlyMap; /** * Process a clear operation. * @param msgEnvelope - The envelope of the message from the server to apply. * @param op - The op to process * @param local - Whether the message originated from the local client * @param localOpMetadata - For local client messages, this is the metadata that was submitted with the message. * For messages from a remote client, this will be undefined. */ processClearMessage(msgEnvelope: ISequencedMessageEnvelope, op: IDirectoryClearOperation, local: boolean, localOpMetadata: ClearLocalOpMetadata | undefined): void; /** * Process a delete operation. * @param msgEnvelope - The envelope of the message from the server to apply. * @param op - The op to process * @param local - Whether the message originated from the local client * @param localOpMetadata - For local client messages, this is the metadata that was submitted with the message. * For messages from a remote client, this will be undefined. */ processDeleteMessage(msgEnvelope: ISequencedMessageEnvelope, op: IDirectoryDeleteOperation, local: boolean, localOpMetadata: EditLocalOpMetadata | undefined): void; /** * Process a set operation. * @param msgEnvelope - The envelope of the message from the server to apply. * @param op - The op to process * @param local - Whether the message originated from the local client * @param localOpMetadata - For local client messages, this is the metadata that was submitted with the message. * For messages from a remote client, this will be undefined. */ processSetMessage(msgEnvelope: ISequencedMessageEnvelope, op: IDirectorySetOperation, value: unknown, local: boolean, localOpMetadata: EditLocalOpMetadata | undefined): void; /** * Process a create subdirectory operation. * @param msgEnvelope - The envelope of the message from the server to apply. * @param op - The op to process * @param local - Whether the message originated from the local client * @param localOpMetadata - For local client messages, this is the metadata that was submitted with the message. * For messages from a remote client, this will be undefined. * @param clientSequenceNumber - The client sequence number of the message. */ processCreateSubDirectoryMessage(msgEnvelope: ISequencedMessageEnvelope, op: IDirectoryCreateSubDirectoryOperation, local: boolean, localOpMetadata: SubDirLocalOpMetadata | undefined, clientSequenceNumber: number): void; /** * Process a delete subdirectory operation. * @param msgEnvelope - The envelope of the message from the server to apply. * @param op - The op to process * @param local - Whether the message originated from the local client * @param localOpMetadata - For local client messages, this is the metadata that was submitted with the message. * For messages from a remote client, this will be undefined. */ processDeleteSubDirectoryMessage(msgEnvelope: ISequencedMessageEnvelope, op: IDirectoryDeleteSubDirectoryOperation, local: boolean, localOpMetadata: SubDirLocalOpMetadata | undefined): void; /** * Submit a clear operation. * @param op - The operation * @param localOpMetadata - The pending operation metadata */ private submitClearMessage; /** * Resubmit a clear operation. * @param op - The operation */ resubmitClearMessage(op: IDirectoryClearOperation, localOpMetadata: ClearLocalOpMetadata): void; /** * Submit a key operation. * @param op - The operation * @param localOpMetadata - The pending operation metadata */ private submitKeyMessage; /** * Submit a key message to remote clients based on a previous submit. * @param op - The map key message * @param localOpMetadata - Metadata from the previous submit */ resubmitKeyMessage(op: IDirectoryKeyOperation, localOpMetadata: EditLocalOpMetadata): void; /** * Submit a create subdirectory operation. * @param op - The operation */ private submitCreateSubDirectoryMessage; /** * Submit a delete subdirectory operation. * @param op - The operation * @param subDir - Any subdirectory deleted by the op */ private submitDeleteSubDirectoryMessage; /** * Submit a subdirectory operation again * @param op - The operation * @param localOpMetadata - metadata submitted with the op originally */ resubmitSubDirectoryMessage(op: IDirectorySubDirectoryOperation, localOpMetadata: SubDirLocalOpMetadata): void; /** * Get the storage of this subdirectory in a serializable format, to be used in snapshotting. * @param serializer - The serializer to use to serialize handles in its values. * @returns The JSONable string representing the storage of this subdirectory */ getSerializedStorage(serializer: IFluidSerializer): Generator<[string, ISerializedValue], void>; getSerializableCreateInfo(): ICreateInfo; /** * Populate a key value in this subdirectory's storage, to be used when loading from snapshot. * @param key - The key to populate * @param localValue - The local value to populate into it */ populateStorage(key: string, value: unknown): void; /** * Populate a subdirectory into this subdirectory, to be used when loading from snapshot. * @param subdirName - The name of the subdirectory to add * @param newSubDir - The new subdirectory to add */ populateSubDirectory(subdirName: string, newSubDir: SubDirectory): void; /** * Rollback a local op * @param op - The operation to rollback * @param localOpMetadata - The local metadata associated with the op. */ rollback(op: any, localOpMetadata: DirectoryLocalOpMetadata): void; /** * Converts the given relative path into an absolute path. * @param path - Relative path to convert * @returns The equivalent absolute path */ private makeAbsolute; /** * This return true if the message is for the current instance of this sub directory. As the sub directory * can be deleted and created again, then this finds if the message is for current instance of directory or not. * @param msgEnvelope - message envelope for the directory * @param targetSubdir - subdirectory instance we are targeting from local op metadata (if a local op) */ private isMessageForCurrentInstanceOfSubDirectory; private registerEventsOnSubDirectory; private disposeSubDirectoryTree; private emitDisposeForSubdirTree; private undisposeSubdirectoryTree; /** * Similar to {@link subdirectories}, but also includes subdirectories that are disposed. */ private getSubdirectoriesEvenIfDisposed; /** * Clears the sequenced data of a subdirectory but notably retains the pending * storage data. This is done when disposing of a directory so if we need to * re-create it, then we still have the pending ops. */ clearSubDirectorySequencedData(): void; } export {}; //# sourceMappingURL=directory.d.ts.map