"use strict";
/*!
 * Copyright (c) Microsoft Corporation and contributors. All rights reserved.
 * Licensed under the MIT License.
 */
Object.defineProperty(exports, "__esModule", { value: true });
exports.currentVersion = exports.FluidClientVersion = exports.withSchemaValidation = exports.unitCodec = exports.ensureBinaryEncoding = exports.withDefaultBinaryEncoding = exports.makeCodecFamily = void 0;
const client_utils_1 = require("@fluid-internal/client-utils");
const internal_1 = require("@fluidframework/core-utils/internal");
/**
 * Creates a codec family from a registry of codecs.
 * Any codec that is not a {@link IMultiFormatCodec} will be wrapped with a default binary encoding.
 */
function makeCodecFamily(registry) {
    const codecs = new Map();
    for (const [formatVersion, codec] of registry) {
        if (codecs.has(formatVersion)) {
            (0, internal_1.fail)(0xabf /* Duplicate codecs specified. */);
        }
        codecs.set(formatVersion, ensureBinaryEncoding(codec));
    }
    return {
        resolve(formatVersion) {
            const codec = codecs.get(formatVersion);
            (0, internal_1.assert)(codec !== undefined, 0x5e6 /* Requested coded for unsupported format. */);
            return codec;
        },
        getSupportedFormats() {
            return codecs.keys();
        },
    };
}
exports.makeCodecFamily = makeCodecFamily;
class DefaultBinaryCodec {
    constructor(jsonCodec) {
        this.jsonCodec = jsonCodec;
    }
    encode(change, context) {
        const jsonable = this.jsonCodec.encode(change, context);
        const json = JSON.stringify(jsonable);
        return client_utils_1.IsoBuffer.from(json);
    }
    decode(change, context) {
        const json = (0, client_utils_1.bufferToString)(change, "utf8");
        const jsonable = JSON.parse(json);
        return this.jsonCodec.decode(jsonable, context);
    }
}
function isJsonCodec(codec) {
    return typeof codec.encode === "function" && typeof codec.decode === "function";
}
/**
 * Constructs a {@link IMultiFormatCodec} from a `IJsonCodec` using a generic binary encoding that simply writes
 * the json representation of the object to a buffer.
 */
function withDefaultBinaryEncoding(jsonCodec) {
    return {
        json: jsonCodec,
        binary: new DefaultBinaryCodec(jsonCodec),
    };
}
exports.withDefaultBinaryEncoding = withDefaultBinaryEncoding;
/**
 * Ensures that the provided single or multi-format codec has a binary encoding.
 * Adapts the json encoding using {@link withDefaultBinaryEncoding} if necessary.
 */
function ensureBinaryEncoding(codec) {
    return isJsonCodec(codec) ? withDefaultBinaryEncoding(codec) : codec;
}
exports.ensureBinaryEncoding = ensureBinaryEncoding;
/**
 * Codec for objects which carry no information.
 */
exports.unitCodec = {
    json: {
        encode: () => 0,
        decode: () => 0,
    },
    binary: {
        encode: () => client_utils_1.IsoBuffer.from(""),
        decode: () => 0,
    },
};
/**
 * Wraps a codec with JSON schema validation for its encoded type.
 * @returns An {@link IJsonCodec} which validates the data it encodes and decodes matches the provided schema.
 */
function withSchemaValidation(schema, codec, validator) {
    if (!validator) {
        return codec;
    }
    const compiledFormat = validator.compile(schema);
    return {
        encode: (obj, context) => {
            const encoded = codec.encode(obj, context);
            if (!compiledFormat.check(encoded)) {
                (0, internal_1.fail)(0xac0 /* Encoded schema should validate */);
            }
            return encoded;
        },
        decode: (encoded, context) => {
            if (!compiledFormat.check(encoded)) {
                (0, internal_1.fail)(0xac1 /* Encoded schema should validate */);
            }
            // TODO: would be nice to provide a more specific validate type to the inner codec than the outer one gets.
            return codec.decode(encoded, context);
        },
    };
}
exports.withSchemaValidation = withSchemaValidation;
/**
 * Versions of Fluid Framework client packages.
 * @remarks
 * Used to express compatibility requirements by indicating the oldest version with which compatibility must be maintained.
 *
 * When no compatibility-impacting change is made in a given version, the value associated with its enum entry may point to the older version which it's fully compatible with.
 * Note that this can change if a future version of the framework introduces an option to use something which is only supported at a particular version. In which case, the values of the enum may shift,
 * but the semantics of keys in this enum will not change.
 *
 * Do not depend on the value of this enums's entries: only depend on the keys (enum members) themselves.
 *
 * Some release may also be omitted if there is currently no need to express that specific version.
 * If the need arises, they might be added in the future.
 *
 * @privateRemarks
 * Entries in these enums should document the user facing impact of opting into a particular version.
 * For example, document if there is an encoding efficiency improvement of oping into that version or newer.
 * Versions with no notable impact can be omitted.
 *
 * These use numeric values for easy threshold comparisons.
 * Without zero padding, version 2.10 is treated as 2.1, which is numerically less than 2.2.
 * Adding leading zeros to the minor version ensures correct comparisons.
 * For example, version 2.20.0 is encoded as 2.020, and version 2.2.0 is encoded as 2.002.
 * For example FF 2.20.0 is encoded as 2.020 and FF 2.2.0 is encoded as 2.002.
 *
 * Three digits was selected as that will likely be enough, while two digits could easily be too few.
 * If three digits ends up being too few, minor releases of 1000 and higher
 * could still be handled using something like 2.999_00001 without having to change the lower releases.
 *
 * This scheme assumes a single version will always be enough to communicate compatibility.
 * For this to work, compatibility has to be strictly increasing.
 * If this is violated (for example a subset of incompatible features from 3.x that are not in 3.0 are back ported to 2.x),
 * a more complex scheme may be needed to allow safely opting into incompatible features in those cases:
 * such a system can be added if/when its needed since it will be opt in and thus non-breaking.
 *
 * TODO: this should likely be defined higher in the stack and specified when creating the container, possibly as part of its schema.
 * TODO: compatibility requirements for how this enum can and cannot be changed should be clarified when/if it's used across multiple layers in the stack.
 * For example, if needed, would adding more leading zeros to the minor version break things.
 * @alpha
 */
var FluidClientVersion;
(function (FluidClientVersion) {
    /**
     * Fluid Framework Client 1.4 and newer.
     * @remarks
     * This opts into support for the 1.4 LTS branch.
     * @privateRemarks
     * As long as this code is in Tree, there is no reason to have this option as SharedTree did not exist in 1.4.
     */
    // v1_4 = 1.004,
    /** Fluid Framework Client 2.0 and newer. */
    FluidClientVersion[FluidClientVersion["v2_0"] = 2] = "v2_0";
    /** Fluid Framework Client 2.1 and newer. */
    // If we think we might want to start allowing opting into something that landed in 2.1 (without opting into something newer),
    // we could add an entry like this to allow users to indicate that they can be opted in once we are ready,
    // then update it to "2.001" once we actually have the opt in working.
    // v2_1 = v2_0,
    /** Fluid Framework Client 2.52 and newer. */
    // New formats introduced in 2.52:
    // - DetachedFieldIndex FormatV2
    FluidClientVersion[FluidClientVersion["v2_52"] = 2.052] = "v2_52";
    /**
     * Enable unreleased and unfinished features.
     * @remarks
     * Using this value can result in documents which can not be opened in future versions of the framework.
     * It can also result in data corruption by enabling unfinished features which may not handle all cases correctly.
     *
     * This can be used with specific APIs when the caller has knowledge of what specific features those APIs will be opted into with it.
     * This is useful for testing features before they are released, but should not be used in production code.
     */
    FluidClientVersion[FluidClientVersion["EnableUnstableFeatures"] = Number.POSITIVE_INFINITY] = "EnableUnstableFeatures";
})(FluidClientVersion || (exports.FluidClientVersion = FluidClientVersion = {}));
/**
 * An up to date version which includes all the important stable features.
 * @remarks
 * Use for cases when data is not persisted and thus would only ever be read by the current version of the framework.
 *
 * @privateRemarks
 * Update as needed.
 * TODO: Consider using packageVersion.ts to keep this current.
 */
exports.currentVersion = FluidClientVersion.v2_0;
//# sourceMappingURL=codec.js.map