/*! * Copyright (c) Microsoft Corporation and contributors. All rights reserved. * Licensed under the MIT License. */ import type { SemanticVersion } from "@fluidframework/runtime-utils/internal"; import type { ITelemetryLoggerExt } from "@fluidframework/telemetry-utils/internal"; /** * Descripe allowed type for properties in document schema. * Please note that for all property types we should use undefined to indicate that particular capability is off. * Using false, or some string value (like "off") will result in clients who do not understand that property failing, whereas * we want them to continue to collaborate alongside clients who support that capability, but such capability is shipping dark for now. * @internal */ export type DocumentSchemaValueType = string | string[] | true | number | undefined; /** * ID Compressor mode. * "on" - compressor is On. It's loaded as part of container load. This mode is sticky - once on, compressor is On for all * sessions for a given document. This results in IContainerRuntime.idCompressor to be always available. * "delayed" - ID compressor bundle is loaded only on establishing of first delta connection, i.e. it does not impact boot of cotnainer. * In such mode IContainerRuntime.idCompressor is not made available (unless previous sessions of same document had it "On"). * The only thing that is available is IContainerRuntime.generateDocumentUniqueId() that provides opportunistically short IDs. * undefined - ID compressor is not loaded. * While IContainerRuntime.generateDocumentUniqueId() is available, it will produce long IDs that are do not compress well. * * @legacy @beta */ export type IdCompressorMode = "on" | "delayed" | undefined; /** * Document schema information. * Describes overall shape of document schema, including unknown (to this version) properties. * * Used by runtime to make a call if it can understand document schema. * If it can't, it should not continue with document and immediately fail, preventing random (cryptic) failures * down the road and potentially corrupting documents. * For now this structure and appropriate interpretation / behavior is focused only on runtime features. * In the future that could be interpolated to more areas, including DDSs used, and even possibly - application * schema. * * Runtime will ignore any properties at the root that it does not understand (i.e. IDocumentSchema.app), but will * stop (and fail session) on any unknown properties within "runtime" sub-tree. * * In most cases values preserved in the document will not dictate if such features should be enabled in a given session. * I.e. if compression is mentioned in document schema, this means that runtime version that opens such document must know * how to interpret such ops, but does not need to actually use compression itself. That said, some options could be * sticky, i.e. influence feature selection for all runtimes opening a document. ID compression is one such example. * Currently there is no mechanism to remove feature from this property bag, i.e. once compression was used, even if it's * disabled (through feature gate or code deployment), all existing documents that used compression will continue to fail * if opened by clients who do not support compression. * * For now we are limiting it to just plain properties, and only really simple types, but that can be changed in the future. * * @internal */ export interface IDocumentSchema { /** * Describes how data needed to understand the schema is stored in this structure. * If runtime sees a version it does not understand, it should immediately fail and not * attempt to interpret any further data. */ version: number; /** * Sequence number when this schema became active. */ refSeq: number; /** * Runtime configurations that affect the document schema. Other clients must understand these * properties to be able to open the document. */ runtime: Record; /** * Info about this document that can be updated via Document Schema change op, but isn't required * to be understood by all clients (unlike the rest of IDocumentSchema properties). Because of this, * some older documents may not have this property, so it's an optional property. */ info?: IDocumentSchemaInfo; } /** * Informational properties of the document that are not subject to strict schema enforcement. * * @internal */ export interface IDocumentSchemaInfo { /** * The minimum version of the FF runtime that should be used to load this document. * Will likely be advanced over time as applications pick up later FF versions. * * We use this to issue telemetry warning events if a client tries to open a document * with a runtime version lower than this. * * See {@link @fluidframework/container-runtime#LoadContainerRuntimeParams} for additional details on `minVersionForCollab`. * * @remarks * We use `SemanticVersion` instead of `MinimumVersionForCollab` since we may open future documents with a * minVersionForCollab version that `MinimumVersionForCollab` does not support. * Note that in such a case (where minVersionForCollab is not a valid `MinimumVersionForCollab`), * loading the document might not work since this version of the runtime may not support it. */ minVersionForCollab: SemanticVersion; } /** * Content of the type=ContainerMessageType.DocumentSchemaChange ops. * The meaning of refSeq field is different in such messages (compared to other usages of IDocumentSchemaCurrent) * ContainerMessageType.DocumentSchemaChange messages use CAS (Compare-and-swap) semantics, and convey * regSeq of last known schema change (known to a client proposing schema change). * @see InboundContainerRuntimeDocumentSchemaMessage * @internal */ export type IDocumentSchemaChangeMessageIncoming = IDocumentSchema; /** * Similar to {@link IDocumentSchemaChangeMessageIncoming}, but used for outgoing schema messages. * @see OutboundContainerRuntimeDocumentSchemaMessage * @internal */ export type IDocumentSchemaChangeMessageOutgoing = IDocumentSchemaCurrent; /** * Settings that this session would like to have, based on options and feature gates. * * WARNING: This type is used to infer IDocumentSchemaCurrent type! * Any changes here (including renaming of properties) are potentially changing document format and should be considered carefully! * * @internal */ export interface IDocumentSchemaFeatures { explicitSchemaControl: boolean; compressionLz4: boolean; idCompressorMode: IdCompressorMode; opGroupingEnabled: boolean; createBlobPayloadPending: true | undefined; /** * List of disallowed versions of the runtime. * This option is sticky. Once a version of runtime is added to this list (when supplied to DocumentsSchemaController's constructor) * it will be added to the list of disallowed versions and stored in document metadata. * Each runtime checks if its version is in this list on container open. If it is, it immediately exits with error message * indicating to the user that this version is no longer supported. * Currently there is no mechanism to remove version from this list. I.e. If it was once added to the list, * it gets added to any document metadata (documents that gets open by this runtime) and there is no way to clear it from document's * metadata. */ disallowedVersions: string[]; } /** * Current version known properties that define document schema * This must be bumped whenever the format of document schema or protocol for changing the current document schema changes * in a way that all old/new clients are required to understand. * Ex: Adding a new configuration property (under IDocumentSchema.runtime) does not require changing this version since there is logic * in old clients for handling new/unknown properties. * Ex: Adding a new property to IDocumentSchema.info does not require changing this version, since info properties are not required to be * understood by all clients. * Ex: Changing the 'document schema acceptance' mechanism from convert-and-swap to one requiring consensus does require changing this version * since all clients need to understand the new protocol. * @internal */ export declare const currentDocumentVersionSchema = 1; /** * Current document schema. * This interface represents the schema that we currently understand and know the * structure of (which properties will be present). * * @internal */ export interface IDocumentSchemaCurrent extends Required { version: typeof currentDocumentVersionSchema; runtime: { [P in keyof IDocumentSchemaFeatures]?: IDocumentSchemaFeatures[P] extends boolean ? true : IDocumentSchemaFeatures[P]; }; } /** * Controller of document schema. * * Recommended pre-reading: https://github.com/microsoft/FluidFramework/blob/main/packages/dds/SchemaVersioning.md * * This class manages current document schema and transitions between document schemas. * At the moment, it only focuses on subset of document schema, specifically - how FluidFramework runtime serializes data * (summary and op format), features & capabilities that a version of runtime has to support and understand in * order to collaborate on a document. * New features that modify document format have to be included in document schema definition. * Usage of such features could only happen after document schema has been updated to reflect such feature. * * This formality allows clients that do not understand such features to fail right away when they observe * document schema listing capabilities that such client does not understand. * Old clients will fail in predictable way. This allows us to * 1) Immediately see such issues and adjust if features are enabled too early, before changes have been saturated. * 2) There is no way to get to 100% saturation with new code. Even if we have 99.99% saturation, there are * still 0.01% of clients who will fail. Failing early and predictably ensures they have no chance to limp along * and potentially corrupt the document. This is especially true for summarizer client, who could simply "undo" * changes it does not understands. * * It's important to note how it overlaps with feature gates and safe velocity. * If new feature was in use, that resulted in a number of documents referencing such feature in document schema. * But, developers (through code deployment or feature gates) could disable usage of such features. * That will stop a process of further document schema changes (for documents that were not using such feature). * And documents that already list such capability in their schema will continue to do so. Later ensures that old * clients who do not understand such feature will continue to fail to open such documents, as such documents very * likely contain data in a new format. * * Controller operates with 4 schemas: * - document schema: whatever we loaded from summary metadata + ops. It follows eventuall consistency rules (i.e. like DDS). * - desired schema - what client is asking for to have (i.e. all the desired settings, based on runtime options / feature gates). * - session schema - current session schema. It's "and" of the above two schemas. * - future schema - "or" of document and desires schemas. * * "or" & "and" operators are defined individually for each property. For Boolean properties it's literally &&, || operators. * But for other properties it's more nuanced. * * Whenver document schema does not match future schema, controller will send an op that attempts to changs documents schema to * future schema. * * Users of this class need to use DocumentsSchemaController.sessionSchema to determine what features can be used. * * There are three modes this class can operate: * 1) Legacy mode. In such mode it does not issue any ops to change document schema. Any changes happen implicitly, * right away, and new features are available right away * 2) Non-legacy mode. In such mode any changes to schema require an op roundtrip. This class will manage such transitions. * However code should assume that any new features that were not enabled in a given document will not be available * for a given session. That's because this session may never send any ops (including read-only documents). Or it may * fail to convert schema. * This class promises eventual movement forward. I.e. if new feature is allowed (let's say - through feature gates), * then eventually all documents that are modified will have that feature reflected in their schema. It could require * multiple reloads / new sessions to get there (depends on if code reacts to schema changes right away, or only consults * schema on document load). * 3) Schema upgrade disabled mode (disableSchemaUpgrade = true). In this mode the controller will never send DocumentSchemaChange ops * and will throw an error if any incoming schema change ops are received. The document schema is effectively frozen at the schema * loaded for this session (snapshot) and will not accept further schema-change ops. * * How schemas are changed (in non-legacy mode): * If a client needs to change a schema, it will attempt to do so as part of normal ops sending process. * Changes happen in CAS (Compare-and-swap) fashion, i.e. client tells current schema and schema it wants to change to. * When a number of clients race to change a schema, then only one of them will win, all others will fail because they will * reference old schema that is no longer in effect. * Clients can retry, but current implementation is simply - they will not (and will rely on next session / reload to do * recalc and decide if schema needs to be changed or not). * * @internal * @sealed */ export declare class DocumentsSchemaController { private readonly onSchemaChange; private readonly disableSchemaUpgrade; private explicitSchemaControl; /** * Have we generated a DocumentSchemaChange op and we're waiting for the ack? * This is used to ensure that we do not generate multiple schema change ops - this client should only ever send one (if any). */ private opPending; private documentSchema; private readonly desiredSchema; private futureSchema; sessionSchema: IDocumentSchemaCurrent; /** * Constructs DocumentsSchemaController that controls current schema and processes around it, including changes in schema. * @param existing - Is the document existing document, or a new doc. * @param documentMetadataSchema - current document's schema, if present. * @param features - features of the document schema that current session wants to see enabled. * @param onSchemaChange - callback that is called whenever schema is changed (not called on creation / load, only when processing document schema change ops) * @param info - Informational properties of the document that are not subject to strict schema enforcement * @param logger - telemetry logger from the runtime * @param disableSchemaUpgrade - when true, the controller will never send or accept DocumentSchemaChange ops */ constructor(existing: boolean, snapshotSequenceNumber: number, documentMetadataSchema: IDocumentSchema | undefined, features: IDocumentSchemaFeatures, onSchemaChange: (schema: IDocumentSchemaCurrent) => void, info: IDocumentSchemaInfo, logger: ITelemetryLoggerExt, disableSchemaUpgrade: boolean); summarizeDocumentSchema(refSeq: number): IDocumentSchema | IDocumentSchemaCurrent | undefined; /** * Called by Container runtime whenever it is about to send some op. * It gives opportunity for controller to issue its own ops - we do not want to send ops if there are no local changes in document. * Please consider note above constructor about race conditions - current design is to generate op only once in a session lifetime. * @returns Optional message to send. Always returns undefined when disableSchemaUpgrade is true. */ maybeGenerateSchemaMessage(): IDocumentSchemaChangeMessageOutgoing | undefined; private validateSeqNumber; /** * Process document schema change messages * Called by ContainerRuntime whenever it sees document schema messages. * When disableSchemaUpgrade is true, an error is thrown if any incoming schema change ops are received. * @param contents - contents of the messages * @param local - whether op is local * @param sequenceNumber - sequence number of the op * @returns true if schema was accepted, otherwise false (rejected due to failed CAS) */ processDocumentSchemaMessages(contents: IDocumentSchemaChangeMessageIncoming[], local: boolean, sequenceNumber: number): boolean; /** * Indicates the pending op was not ack'd and we may try to send it again if needed. */ pendingOpNotAcked(): void; } //# sourceMappingURL=documentSchema.d.ts.map