@fluidframework/id-compressor 2.0.0-dev-rc.1.0.0.225277 → 2.0.0-dev-rc.1.0.0.232845
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +12 -0
- package/README.md +0 -6
- package/api-report/id-compressor.api.md +4 -39
- package/dist/{appendOnlySortedMap.cjs → appendOnlySortedMap.js} +1 -1
- package/dist/appendOnlySortedMap.js.map +1 -0
- package/dist/{finalSpace.cjs → finalSpace.js} +2 -2
- package/dist/finalSpace.js.map +1 -0
- package/dist/id-compressor-alpha.d.ts +24 -59
- package/dist/id-compressor-beta.d.ts +1 -2
- package/dist/id-compressor-public.d.ts +1 -2
- package/dist/id-compressor-untrimmed.d.ts +24 -59
- package/dist/idCompressor.d.ts +13 -17
- package/dist/idCompressor.d.ts.map +1 -1
- package/dist/{idCompressor.cjs → idCompressor.js} +94 -55
- package/dist/idCompressor.js.map +1 -0
- package/dist/{identifiers.cjs → identifiers.js} +1 -1
- package/dist/identifiers.js.map +1 -0
- package/dist/index.d.ts +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/{index.cjs → index.js} +4 -5
- package/dist/index.js.map +1 -0
- package/dist/packageVersion.d.ts +1 -1
- package/dist/{packageVersion.cjs → packageVersion.js} +2 -2
- package/dist/packageVersion.js.map +1 -0
- package/dist/{persistanceUtilities.cjs → persistanceUtilities.js} +1 -1
- package/dist/persistanceUtilities.js.map +1 -0
- package/dist/{sessionSpaceNormalizer.cjs → sessionSpaceNormalizer.js} +3 -3
- package/dist/sessionSpaceNormalizer.js.map +1 -0
- package/dist/{sessions.cjs → sessions.js} +5 -8
- package/dist/sessions.js.map +1 -0
- package/dist/tsdoc-metadata.json +1 -1
- package/dist/types/idCompressor.d.ts +21 -0
- package/dist/types/idCompressor.d.ts.map +1 -1
- package/dist/types/{identifiers.cjs → idCompressor.js} +1 -1
- package/dist/types/idCompressor.js.map +1 -0
- package/dist/types/identifiers.js +7 -0
- package/dist/types/identifiers.js.map +1 -0
- package/dist/types/{persisted-types/0.0.1.cjs → index.js} +1 -1
- package/dist/types/index.js.map +1 -0
- package/dist/types/{index.cjs → persisted-types/0.0.1.js} +1 -1
- package/dist/types/persisted-types/0.0.1.js.map +1 -0
- package/dist/types/persisted-types/{index.cjs → index.js} +1 -1
- package/dist/types/persisted-types/index.js.map +1 -0
- package/dist/{utilities.cjs → utilities.js} +1 -1
- package/dist/utilities.js.map +1 -0
- package/lib/id-compressor-alpha.d.mts +24 -59
- package/lib/id-compressor-beta.d.mts +1 -2
- package/lib/id-compressor-public.d.mts +1 -2
- package/lib/id-compressor-untrimmed.d.mts +24 -59
- package/lib/idCompressor.d.mts +13 -17
- package/lib/idCompressor.d.mts.map +1 -1
- package/lib/idCompressor.mjs +88 -49
- package/lib/idCompressor.mjs.map +1 -1
- package/lib/index.d.mts +1 -1
- package/lib/index.d.mts.map +1 -1
- package/lib/index.mjs +1 -1
- package/lib/index.mjs.map +1 -1
- package/lib/packageVersion.d.mts +1 -1
- package/lib/packageVersion.mjs +1 -1
- package/lib/packageVersion.mjs.map +1 -1
- package/lib/sessions.mjs +1 -1
- package/lib/sessions.mjs.map +1 -1
- package/lib/types/idCompressor.d.mts +21 -0
- package/lib/types/idCompressor.d.mts.map +1 -1
- package/lib/types/idCompressor.mjs.map +1 -1
- package/package.json +16 -15
- package/src/idCompressor.ts +125 -66
- package/src/index.ts +1 -1
- package/src/packageVersion.ts +1 -1
- package/src/sessions.ts +1 -1
- package/src/types/idCompressor.ts +22 -0
- package/dist/appendOnlySortedMap.cjs.map +0 -1
- package/dist/finalSpace.cjs.map +0 -1
- package/dist/idCompressor.cjs.map +0 -1
- package/dist/identifiers.cjs.map +0 -1
- package/dist/index.cjs.map +0 -1
- package/dist/packageVersion.cjs.map +0 -1
- package/dist/persistanceUtilities.cjs.map +0 -1
- package/dist/sessionSpaceNormalizer.cjs.map +0 -1
- package/dist/sessions.cjs.map +0 -1
- package/dist/types/idCompressor.cjs +0 -7
- package/dist/types/idCompressor.cjs.map +0 -1
- package/dist/types/identifiers.cjs.map +0 -1
- package/dist/types/index.cjs.map +0 -1
- package/dist/types/persisted-types/0.0.1.cjs.map +0 -1
- package/dist/types/persisted-types/index.cjs.map +0 -1
- package/dist/utilities.cjs.map +0 -1
- package/tsc-multi.test.json +0 -4
- /package/{.eslintrc.js → .eslintrc.cjs} +0 -0
|
@@ -20,6 +20,27 @@ export interface IIdCompressorCore {
|
|
|
20
20
|
* @param range - the range of session-local IDs to finalize.
|
|
21
21
|
*/
|
|
22
22
|
finalizeCreationRange(range: IdCreationRange): void;
|
|
23
|
+
/**
|
|
24
|
+
* Run a callback that is performed from the perspective of a special "ghost" session.
|
|
25
|
+
* Any ids generated by this session will be immediately finalized on the local client as if
|
|
26
|
+
* they were created by a remote client with `ghostSessionId`.
|
|
27
|
+
*
|
|
28
|
+
* *WARNING:* This API requires an external consensus mechanism to safely use:
|
|
29
|
+
* In an attached container (i.e. multiple clients may have the document loaded), all clients must guarantee that:
|
|
30
|
+
* - They invoke this API starting from the same finalized creation ranges
|
|
31
|
+
* - This API is invoked with the same ghost session id
|
|
32
|
+
* - `ghostSessionCallback` deterministically mints the same number of ids on each client within the ghost session
|
|
33
|
+
* Failure to meet these requirement will result in divergence across clients and eventual consistency errors.
|
|
34
|
+
* While the ghost sesion callback is running, IdCompressor does not support serialization.
|
|
35
|
+
* @remarks This API is primarily intended for data migration scenarios which are able to deterministically transform
|
|
36
|
+
* data in some format into data in a new format.
|
|
37
|
+
* The first requirement (that all clients must invoke the API with the same finalized creation ranges) is guaranteed
|
|
38
|
+
* for this scenario because the data transformation callback occurs at a specific ack within the op stream on all
|
|
39
|
+
* clients, and clients don't finalize creation ranges for local changes they might have at this point in time.
|
|
40
|
+
* @param ghostSessionId - The session id that minted ids generated within `ghostSessionCallback` should be attributed to.
|
|
41
|
+
* @param ghostSessionCallback - Callback which mints ids attributed to the ghost session.
|
|
42
|
+
*/
|
|
43
|
+
beginGhostSession(ghostSessionId: SessionId, ghostSessionCallback: () => void): any;
|
|
23
44
|
/**
|
|
24
45
|
* Returns a persistable form of the current state of this `IdCompressor` which can be rehydrated via `IdCompressor.deserialize()`.
|
|
25
46
|
* This includes finalized state as well as un-finalized state and is therefore suitable for use in offline scenarios.
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"idCompressor.d.mts","sourceRoot":"","sources":["../../src/types/idCompressor.ts"],"names":[],"mappings":"AAAA;;;GAGG;OAEI,EAAE,wBAAwB,EAAE,mBAAmB,EAAE,SAAS,EAAE,QAAQ,EAAE;OACtE,EACN,eAAe,EACf,mCAAmC,EACnC,wCAAwC,EACxC;AAED;;GAEG;AACH,MAAM,WAAW,iBAAiB;IACjC;;;;;OAKG;IACH,qBAAqB,IAAI,eAAe,CAAC;IAEzC;;;OAGG;IACH,qBAAqB,CAAC,KAAK,EAAE,eAAe,GAAG,IAAI,CAAC;IAEpD;;;OAGG;IACH,SAAS,CAAC,WAAW,EAAE,IAAI,GAAG,wCAAwC,CAAC;IAEvE;;;OAGG;IACH,SAAS,CAAC,WAAW,EAAE,KAAK,GAAG,mCAAmC,CAAC;CACnE;AAED;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA6DG;AACH,MAAM,WAAW,aAAa;IAC7B,cAAc,EAAE,SAAS,CAAC;IAE1B;;;;OAIG;IACH,oBAAoB,IAAI,wBAAwB,CAAC;IACjD;;;;OAIG;IACH,kBAAkB,CAAC,EAAE,EAAE,wBAAwB,GAAG,mBAAmB,CAAC;IACtE;;;;;;;;OAQG;IACH,uBAAuB,CACtB,EAAE,EAAE,mBAAmB,EACvB,eAAe,EAAE,SAAS,GACxB,wBAAwB,CAAC;IAE5B;;;;OAIG;IACH,UAAU,CAAC,EAAE,EAAE,wBAAwB,GAAG,QAAQ,CAAC;IAEnD;;;;OAIG;IACH,UAAU,CAAC,YAAY,EAAE,QAAQ,GAAG,wBAAwB,CAAC;IAE7D;;;;OAIG;IACH,aAAa,CAAC,YAAY,EAAE,QAAQ,GAAG,wBAAwB,GAAG,SAAS,CAAC;CAC5E"}
|
|
1
|
+
{"version":3,"file":"idCompressor.d.mts","sourceRoot":"","sources":["../../src/types/idCompressor.ts"],"names":[],"mappings":"AAAA;;;GAGG;OAEI,EAAE,wBAAwB,EAAE,mBAAmB,EAAE,SAAS,EAAE,QAAQ,EAAE;OACtE,EACN,eAAe,EACf,mCAAmC,EACnC,wCAAwC,EACxC;AAED;;GAEG;AACH,MAAM,WAAW,iBAAiB;IACjC;;;;;OAKG;IACH,qBAAqB,IAAI,eAAe,CAAC;IAEzC;;;OAGG;IACH,qBAAqB,CAAC,KAAK,EAAE,eAAe,GAAG,IAAI,CAAC;IAEpD;;;;;;;;;;;;;;;;;;;OAmBG;IACH,iBAAiB,CAAC,cAAc,EAAE,SAAS,EAAE,oBAAoB,EAAE,MAAM,IAAI,OAAE;IAE/E;;;OAGG;IACH,SAAS,CAAC,WAAW,EAAE,IAAI,GAAG,wCAAwC,CAAC;IAEvE;;;OAGG;IACH,SAAS,CAAC,WAAW,EAAE,KAAK,GAAG,mCAAmC,CAAC;CACnE;AAED;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA6DG;AACH,MAAM,WAAW,aAAa;IAC7B,cAAc,EAAE,SAAS,CAAC;IAE1B;;;;OAIG;IACH,oBAAoB,IAAI,wBAAwB,CAAC;IACjD;;;;OAIG;IACH,kBAAkB,CAAC,EAAE,EAAE,wBAAwB,GAAG,mBAAmB,CAAC;IACtE;;;;;;;;OAQG;IACH,uBAAuB,CACtB,EAAE,EAAE,mBAAmB,EACvB,eAAe,EAAE,SAAS,GACxB,wBAAwB,CAAC;IAE5B;;;;OAIG;IACH,UAAU,CAAC,EAAE,EAAE,wBAAwB,GAAG,QAAQ,CAAC;IAEnD;;;;OAIG;IACH,UAAU,CAAC,YAAY,EAAE,QAAQ,GAAG,wBAAwB,CAAC;IAE7D;;;;OAIG;IACH,aAAa,CAAC,YAAY,EAAE,QAAQ,GAAG,wBAAwB,GAAG,SAAS,CAAC;CAC5E"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"idCompressor.mjs","sourceRoot":"","sources":["../../src/types/idCompressor.ts"],"names":[],"mappings":"AAAA;;;GAGG","sourcesContent":["/*!\n * Copyright (c) Microsoft Corporation and contributors. All rights reserved.\n * Licensed under the MIT License.\n */\n\nimport { SessionSpaceCompressedId, OpSpaceCompressedId, SessionId, StableId } from \"./identifiers\";\nimport {\n\tIdCreationRange,\n\tSerializedIdCompressorWithNoSession,\n\tSerializedIdCompressorWithOngoingSession,\n} from \"./persisted-types\";\n\n/**\n * @alpha\n */\nexport interface IIdCompressorCore {\n\t/**\n\t * Returns a range of IDs created by this session in a format for sending to the server for finalizing.\n\t * The range will include all IDs generated via calls to `generateCompressedId` since the last time this method was called.\n\t * @returns the range of IDs, which may be empty. This range must be sent to the server for ordering before\n\t * it is finalized. Ranges must be sent to the server in the order that they are taken via calls to this method.\n\t */\n\ttakeNextCreationRange(): IdCreationRange;\n\n\t/**\n\t * Finalizes the supplied range of IDs (which may be from either a remote or local session).\n\t * @param range - the range of session-local IDs to finalize.\n\t */\n\tfinalizeCreationRange(range: IdCreationRange): void;\n\n\t/**\n\t * Returns a persistable form of the current state of this `IdCompressor` which can be rehydrated via `IdCompressor.deserialize()`.\n\t * This includes finalized state as well as un-finalized state and is therefore suitable for use in offline scenarios.\n\t */\n\tserialize(withSession: true): SerializedIdCompressorWithOngoingSession;\n\n\t/**\n\t * Returns a persistable form of the current state of this `IdCompressor` which can be rehydrated via `IdCompressor.deserialize()`.\n\t * This only includes finalized state and is therefore suitable for use in summaries.\n\t */\n\tserialize(withSession: false): SerializedIdCompressorWithNoSession;\n}\n\n/**\n * A distributed UUID generator and compressor.\n *\n * Generates arbitrary non-colliding v4 UUIDs, called stable IDs, for multiple \"sessions\" (which can be distributed across the network),\n * providing each session with the ability to map these UUIDs to `numbers`.\n *\n * A session is a unique identifier that denotes a single compressor. New IDs are created through a single compressor API\n * which should then sent in ranges to the server for total ordering (and are subsequently relayed to other clients). When a new ID is\n * created it is said to be created by the compressor's \"local\" session.\n *\n * For each stable ID created, two numeric IDs are provided by the compressor:\n *\n * 1. A session-local ID, which is stable for the lifetime of the session (which could be longer than that of the compressor object, as it may\n * be serialized for offline usage). Available as soon as the stable ID is allocated. These IDs are session-unique and are thus only\n * safely usable within the scope of the compressor that created it.\n *\n * 2. A final ID, which is stable across serialization and deserialization of an IdCompressor. Available as soon as the range containing\n * the corresponding session-local ID is totally ordered (via consensus) with respect to other sessions' allocations.\n * Final IDs are known to and publicly usable by any compressor that has received them.\n *\n * Compressors will allocate UUIDs in non-random ways to reduce entropy allowing for optimized storage of the data needed\n * to map the UUIDs to the numbers.\n *\n * The following invariants are upheld by IdCompressor:\n *\n * 1. Session-local IDs will always decompress to the same UUIDs for the lifetime of the session.\n *\n * 2. Final IDs will always decompress to the same UUIDs.\n *\n * 3. After a server-processed range of session-local IDs (from any session) is received by a compressor, any of those session-local IDs may be\n * translated by the compressor into the corresponding final ID. For any given session-local ID, this translation will always yield the\n * same final ID.\n *\n * 4. A UUID will always compress into the same session-local ID for the lifetime of the session.\n *\n * Session-local IDs are sent across the wire in efficiently-represented ranges. These ranges are created by querying the compressor, and *must*\n * be ordered (i.e. sent to the server) in the order they are created in order to preserve the above invariants.\n *\n * Session-local IDs can be used immediately after creation, but will eventually (after being sequenced) have a corresponding final ID. This\n * could make reasoning about equality of those two forms difficult. For example, if a cache is keyed off of a\n * session-local ID but is later queried using the final ID (which is semantically equal, as it decompresses to the same UUID/string) it will\n * produce a cache miss. In order to make using collections of both remotely created and locally created IDs easy, regardless of whether the\n * session-local IDs have been finalized, the compressor defines two \"spaces\" of IDs:\n *\n * 1. Session space: in this space, all IDs are normalized to their \"most local form\". This means that all IDs created by the local session\n * will be in local form, regardless of if they have been finalized. Remotely created IDs, which could only have been received after\n * finalizing and will never have a local form for the compressor, will of course be final IDs. This space should be used with consumer APIs\n * and data structures, as the lifetime of the IDs is guaranteed to be the same as the compressor object. Care must be taken to not use\n * these IDs across compressor objects, as the local IDs are specific to the compressor that created them.\n *\n * 2. Op space: in this space, all IDs are normalized to their \"most final form\". This means that all IDs except session-local IDs that\n * have not yet been finalized will be in final ID form. This space is useful for serialization in ops (e.g. references), as other clients\n * that receive them need not do any work to normalize them to *their* session-space in the common case. Note that IDs in op space may move\n * out of Op space over time, namely, when a session-local ID in this space becomes finalized, and thereafter has a \"more final form\".\n * Consequentially, it may be useful to restrict parameters of a persisted type to this space (to optimize perf), but it is potentially\n * incorrect to use this type for a runtime variable. This is an asymmetry that does not affect session space, as local IDs are always as\n * \"local as possible\".\n *\n * These two spaces naturally define a rule: consumers of compressed IDs should use session-space IDs, but serialized forms such as ops\n * should use op-space IDs.\n * @public\n */\nexport interface IIdCompressor {\n\tlocalSessionId: SessionId;\n\n\t/**\n\t * Generates a new compressed ID or returns an existing one.\n\t * This should ONLY be called to generate IDs for local operations.\n\t * @returns A new local ID in session space.\n\t */\n\tgenerateCompressedId(): SessionSpaceCompressedId;\n\t/**\n\t * Normalizes a session space ID into op space.\n\t * @param id - the local ID to normalize.\n\t * @returns the ID in op space.\n\t */\n\tnormalizeToOpSpace(id: SessionSpaceCompressedId): OpSpaceCompressedId;\n\t/**\n\t * Normalizes an ID into session space.\n\t * @param id - the ID to normalize. If it is a local ID, it is assumed to have been created by the session corresponding\n\t * to `sessionId`.\n\t * @param originSessionId - the session from which `id` originated\n\t * @returns the session-space ID corresponding to `id`, which might not have been a final ID if the client that created it had not yet\n\t * finalized it. This can occur when a client references an ID during the window of time in which it is waiting to receive the ordered\n\t * range that contained it from the server.\n\t */\n\tnormalizeToSessionSpace(\n\t\tid: OpSpaceCompressedId,\n\t\toriginSessionId: SessionId,\n\t): SessionSpaceCompressedId;\n\n\t/**\n\t * Decompresses a previously compressed ID into a UUID.\n\t * @param id - the compressed ID to be decompressed.\n\t * @returns the UUID associated with the compressed ID. Fails if the ID was not generated by this compressor.\n\t */\n\tdecompress(id: SessionSpaceCompressedId): StableId;\n\n\t/**\n\t * Recompresses a UUID.\n\t * @param uncompressed - the UUID to recompress.\n\t * @returns the `CompressedId` associated with `uncompressed`. Fails if it has not been previously compressed by this compressor.\n\t */\n\trecompress(uncompressed: StableId): SessionSpaceCompressedId;\n\n\t/**\n\t * Attempts to recompresses a UUID.\n\t * @param uncompressed - the UUID to recompress,\n\t * @returns the `CompressedId` associated with `uncompressed` or undefined if it has not been previously compressed by this compressor.\n\t */\n\ttryRecompress(uncompressed: StableId): SessionSpaceCompressedId | undefined;\n}\n"]}
|
|
1
|
+
{"version":3,"file":"idCompressor.mjs","sourceRoot":"","sources":["../../src/types/idCompressor.ts"],"names":[],"mappings":"AAAA;;;GAGG","sourcesContent":["/*!\n * Copyright (c) Microsoft Corporation and contributors. All rights reserved.\n * Licensed under the MIT License.\n */\n\nimport { SessionSpaceCompressedId, OpSpaceCompressedId, SessionId, StableId } from \"./identifiers\";\nimport {\n\tIdCreationRange,\n\tSerializedIdCompressorWithNoSession,\n\tSerializedIdCompressorWithOngoingSession,\n} from \"./persisted-types\";\n\n/**\n * @alpha\n */\nexport interface IIdCompressorCore {\n\t/**\n\t * Returns a range of IDs created by this session in a format for sending to the server for finalizing.\n\t * The range will include all IDs generated via calls to `generateCompressedId` since the last time this method was called.\n\t * @returns the range of IDs, which may be empty. This range must be sent to the server for ordering before\n\t * it is finalized. Ranges must be sent to the server in the order that they are taken via calls to this method.\n\t */\n\ttakeNextCreationRange(): IdCreationRange;\n\n\t/**\n\t * Finalizes the supplied range of IDs (which may be from either a remote or local session).\n\t * @param range - the range of session-local IDs to finalize.\n\t */\n\tfinalizeCreationRange(range: IdCreationRange): void;\n\n\t/**\n\t * Run a callback that is performed from the perspective of a special \"ghost\" session.\n\t * Any ids generated by this session will be immediately finalized on the local client as if\n\t * they were created by a remote client with `ghostSessionId`.\n\t *\n\t * *WARNING:* This API requires an external consensus mechanism to safely use:\n\t * In an attached container (i.e. multiple clients may have the document loaded), all clients must guarantee that:\n\t * - They invoke this API starting from the same finalized creation ranges\n\t * - This API is invoked with the same ghost session id\n\t * - `ghostSessionCallback` deterministically mints the same number of ids on each client within the ghost session\n\t * Failure to meet these requirement will result in divergence across clients and eventual consistency errors.\n\t * While the ghost sesion callback is running, IdCompressor does not support serialization.\n\t * @remarks This API is primarily intended for data migration scenarios which are able to deterministically transform\n\t * data in some format into data in a new format.\n\t * The first requirement (that all clients must invoke the API with the same finalized creation ranges) is guaranteed\n\t * for this scenario because the data transformation callback occurs at a specific ack within the op stream on all\n\t * clients, and clients don't finalize creation ranges for local changes they might have at this point in time.\n\t * @param ghostSessionId - The session id that minted ids generated within `ghostSessionCallback` should be attributed to.\n\t * @param ghostSessionCallback - Callback which mints ids attributed to the ghost session.\n\t */\n\tbeginGhostSession(ghostSessionId: SessionId, ghostSessionCallback: () => void);\n\n\t/**\n\t * Returns a persistable form of the current state of this `IdCompressor` which can be rehydrated via `IdCompressor.deserialize()`.\n\t * This includes finalized state as well as un-finalized state and is therefore suitable for use in offline scenarios.\n\t */\n\tserialize(withSession: true): SerializedIdCompressorWithOngoingSession;\n\n\t/**\n\t * Returns a persistable form of the current state of this `IdCompressor` which can be rehydrated via `IdCompressor.deserialize()`.\n\t * This only includes finalized state and is therefore suitable for use in summaries.\n\t */\n\tserialize(withSession: false): SerializedIdCompressorWithNoSession;\n}\n\n/**\n * A distributed UUID generator and compressor.\n *\n * Generates arbitrary non-colliding v4 UUIDs, called stable IDs, for multiple \"sessions\" (which can be distributed across the network),\n * providing each session with the ability to map these UUIDs to `numbers`.\n *\n * A session is a unique identifier that denotes a single compressor. New IDs are created through a single compressor API\n * which should then sent in ranges to the server for total ordering (and are subsequently relayed to other clients). When a new ID is\n * created it is said to be created by the compressor's \"local\" session.\n *\n * For each stable ID created, two numeric IDs are provided by the compressor:\n *\n * 1. A session-local ID, which is stable for the lifetime of the session (which could be longer than that of the compressor object, as it may\n * be serialized for offline usage). Available as soon as the stable ID is allocated. These IDs are session-unique and are thus only\n * safely usable within the scope of the compressor that created it.\n *\n * 2. A final ID, which is stable across serialization and deserialization of an IdCompressor. Available as soon as the range containing\n * the corresponding session-local ID is totally ordered (via consensus) with respect to other sessions' allocations.\n * Final IDs are known to and publicly usable by any compressor that has received them.\n *\n * Compressors will allocate UUIDs in non-random ways to reduce entropy allowing for optimized storage of the data needed\n * to map the UUIDs to the numbers.\n *\n * The following invariants are upheld by IdCompressor:\n *\n * 1. Session-local IDs will always decompress to the same UUIDs for the lifetime of the session.\n *\n * 2. Final IDs will always decompress to the same UUIDs.\n *\n * 3. After a server-processed range of session-local IDs (from any session) is received by a compressor, any of those session-local IDs may be\n * translated by the compressor into the corresponding final ID. For any given session-local ID, this translation will always yield the\n * same final ID.\n *\n * 4. A UUID will always compress into the same session-local ID for the lifetime of the session.\n *\n * Session-local IDs are sent across the wire in efficiently-represented ranges. These ranges are created by querying the compressor, and *must*\n * be ordered (i.e. sent to the server) in the order they are created in order to preserve the above invariants.\n *\n * Session-local IDs can be used immediately after creation, but will eventually (after being sequenced) have a corresponding final ID. This\n * could make reasoning about equality of those two forms difficult. For example, if a cache is keyed off of a\n * session-local ID but is later queried using the final ID (which is semantically equal, as it decompresses to the same UUID/string) it will\n * produce a cache miss. In order to make using collections of both remotely created and locally created IDs easy, regardless of whether the\n * session-local IDs have been finalized, the compressor defines two \"spaces\" of IDs:\n *\n * 1. Session space: in this space, all IDs are normalized to their \"most local form\". This means that all IDs created by the local session\n * will be in local form, regardless of if they have been finalized. Remotely created IDs, which could only have been received after\n * finalizing and will never have a local form for the compressor, will of course be final IDs. This space should be used with consumer APIs\n * and data structures, as the lifetime of the IDs is guaranteed to be the same as the compressor object. Care must be taken to not use\n * these IDs across compressor objects, as the local IDs are specific to the compressor that created them.\n *\n * 2. Op space: in this space, all IDs are normalized to their \"most final form\". This means that all IDs except session-local IDs that\n * have not yet been finalized will be in final ID form. This space is useful for serialization in ops (e.g. references), as other clients\n * that receive them need not do any work to normalize them to *their* session-space in the common case. Note that IDs in op space may move\n * out of Op space over time, namely, when a session-local ID in this space becomes finalized, and thereafter has a \"more final form\".\n * Consequentially, it may be useful to restrict parameters of a persisted type to this space (to optimize perf), but it is potentially\n * incorrect to use this type for a runtime variable. This is an asymmetry that does not affect session space, as local IDs are always as\n * \"local as possible\".\n *\n * These two spaces naturally define a rule: consumers of compressed IDs should use session-space IDs, but serialized forms such as ops\n * should use op-space IDs.\n * @public\n */\nexport interface IIdCompressor {\n\tlocalSessionId: SessionId;\n\n\t/**\n\t * Generates a new compressed ID or returns an existing one.\n\t * This should ONLY be called to generate IDs for local operations.\n\t * @returns A new local ID in session space.\n\t */\n\tgenerateCompressedId(): SessionSpaceCompressedId;\n\t/**\n\t * Normalizes a session space ID into op space.\n\t * @param id - the local ID to normalize.\n\t * @returns the ID in op space.\n\t */\n\tnormalizeToOpSpace(id: SessionSpaceCompressedId): OpSpaceCompressedId;\n\t/**\n\t * Normalizes an ID into session space.\n\t * @param id - the ID to normalize. If it is a local ID, it is assumed to have been created by the session corresponding\n\t * to `sessionId`.\n\t * @param originSessionId - the session from which `id` originated\n\t * @returns the session-space ID corresponding to `id`, which might not have been a final ID if the client that created it had not yet\n\t * finalized it. This can occur when a client references an ID during the window of time in which it is waiting to receive the ordered\n\t * range that contained it from the server.\n\t */\n\tnormalizeToSessionSpace(\n\t\tid: OpSpaceCompressedId,\n\t\toriginSessionId: SessionId,\n\t): SessionSpaceCompressedId;\n\n\t/**\n\t * Decompresses a previously compressed ID into a UUID.\n\t * @param id - the compressed ID to be decompressed.\n\t * @returns the UUID associated with the compressed ID. Fails if the ID was not generated by this compressor.\n\t */\n\tdecompress(id: SessionSpaceCompressedId): StableId;\n\n\t/**\n\t * Recompresses a UUID.\n\t * @param uncompressed - the UUID to recompress.\n\t * @returns the `CompressedId` associated with `uncompressed`. Fails if it has not been previously compressed by this compressor.\n\t */\n\trecompress(uncompressed: StableId): SessionSpaceCompressedId;\n\n\t/**\n\t * Attempts to recompresses a UUID.\n\t * @param uncompressed - the UUID to recompress,\n\t * @returns the `CompressedId` associated with `uncompressed` or undefined if it has not been previously compressed by this compressor.\n\t */\n\ttryRecompress(uncompressed: StableId): SessionSpaceCompressedId | undefined;\n}\n"]}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@fluidframework/id-compressor",
|
|
3
|
-
"version": "2.0.0-dev-rc.1.0.0.
|
|
3
|
+
"version": "2.0.0-dev-rc.1.0.0.232845",
|
|
4
4
|
"description": "ID compressor",
|
|
5
5
|
"homepage": "https://fluidframework.com",
|
|
6
6
|
"repository": {
|
|
@@ -11,7 +11,7 @@
|
|
|
11
11
|
"license": "MIT",
|
|
12
12
|
"author": "Microsoft and contributors",
|
|
13
13
|
"sideEffects": false,
|
|
14
|
-
"main": "dist/index.
|
|
14
|
+
"main": "dist/index.js",
|
|
15
15
|
"module": "lib/index.mjs",
|
|
16
16
|
"types": "dist/index.d.ts",
|
|
17
17
|
"c8": {
|
|
@@ -35,24 +35,24 @@
|
|
|
35
35
|
"temp-directory": "nyc/.nyc_output"
|
|
36
36
|
},
|
|
37
37
|
"dependencies": {
|
|
38
|
-
"@fluid-internal/client-utils": "2.0.0-dev-rc.1.0.0.
|
|
39
|
-
"@fluidframework/core-interfaces": "2.0.0-dev-rc.1.0.0.
|
|
40
|
-
"@fluidframework/core-utils": "2.0.0-dev-rc.1.0.0.
|
|
41
|
-
"@fluidframework/telemetry-utils": "2.0.0-dev-rc.1.0.0.
|
|
42
|
-
"sorted-btree": "^1.8.0",
|
|
38
|
+
"@fluid-internal/client-utils": "2.0.0-dev-rc.1.0.0.232845",
|
|
39
|
+
"@fluidframework/core-interfaces": "2.0.0-dev-rc.1.0.0.232845",
|
|
40
|
+
"@fluidframework/core-utils": "2.0.0-dev-rc.1.0.0.232845",
|
|
41
|
+
"@fluidframework/telemetry-utils": "2.0.0-dev-rc.1.0.0.232845",
|
|
42
|
+
"@tylerbu/sorted-btree-es6": "^1.8.0",
|
|
43
43
|
"uuid": "^9.0.0"
|
|
44
44
|
},
|
|
45
45
|
"devDependencies": {
|
|
46
46
|
"@arethetypeswrong/cli": "^0.13.3",
|
|
47
|
-
"@fluid-private/stochastic-test-utils": "2.0.0-dev-rc.1.0.0.
|
|
47
|
+
"@fluid-private/stochastic-test-utils": "2.0.0-dev-rc.1.0.0.232845",
|
|
48
48
|
"@fluid-tools/benchmark": "^0.48.0",
|
|
49
|
-
"@fluid-tools/build-cli": "0.29.0
|
|
49
|
+
"@fluid-tools/build-cli": "^0.29.0",
|
|
50
50
|
"@fluidframework/build-common": "^2.0.3",
|
|
51
|
-
"@fluidframework/build-tools": "0.29.0
|
|
52
|
-
"@fluidframework/eslint-config-fluid": "^3.
|
|
51
|
+
"@fluidframework/build-tools": "^0.29.0",
|
|
52
|
+
"@fluidframework/eslint-config-fluid": "^3.3.0",
|
|
53
53
|
"@fluidframework/id-compressor-previous": "npm:@fluidframework/id-compressor@2.0.0-internal.8.0.0",
|
|
54
|
-
"@fluidframework/mocha-test-setup": "2.0.0-dev-rc.1.0.0.
|
|
55
|
-
"@microsoft/api-extractor": "^7.
|
|
54
|
+
"@fluidframework/mocha-test-setup": "2.0.0-dev-rc.1.0.0.232845",
|
|
55
|
+
"@microsoft/api-extractor": "^7.39.1",
|
|
56
56
|
"@types/mocha": "^9.1.1",
|
|
57
57
|
"@types/uuid": "^9.0.2",
|
|
58
58
|
"c8": "^8.0.1",
|
|
@@ -93,7 +93,7 @@
|
|
|
93
93
|
"build:docs": "fluid-build . --task api",
|
|
94
94
|
"build:esnext": "tsc-multi --config ../../../common/build/build-common/tsc-multi.esm.json",
|
|
95
95
|
"build:genver": "gen-version",
|
|
96
|
-
"build:test": "tsc
|
|
96
|
+
"build:test": "tsc --project ./src/test/tsconfig.json",
|
|
97
97
|
"check:are-the-types-wrong": "attw --pack",
|
|
98
98
|
"check:release-tags": "api-extractor run --local --config ./api-extractor-lint.json",
|
|
99
99
|
"ci:build:docs": "api-extractor run",
|
|
@@ -110,7 +110,8 @@
|
|
|
110
110
|
"test:coverage": "c8 npm test",
|
|
111
111
|
"test:mocha": "mocha --ignore \"dist/test/types/*\" --recursive dist/test -r node_modules/@fluidframework/mocha-test-setup",
|
|
112
112
|
"test:mocha:verbose": "cross-env FLUID_TEST_VERBOSE=1 npm run test:mocha",
|
|
113
|
-
"
|
|
113
|
+
"test:snapshots:regen": "npm run test:mocha -- --snapshot",
|
|
114
|
+
"tsc": "tsc",
|
|
114
115
|
"tsc:watch": "tsc --watch"
|
|
115
116
|
}
|
|
116
117
|
}
|
package/src/idCompressor.ts
CHANGED
|
@@ -45,6 +45,7 @@ import {
|
|
|
45
45
|
lastFinalizedLocal,
|
|
46
46
|
Session,
|
|
47
47
|
Sessions,
|
|
48
|
+
lastFinalizedFinal,
|
|
48
49
|
} from "./sessions";
|
|
49
50
|
import { SessionSpaceNormalizer } from "./sessionSpaceNormalizer";
|
|
50
51
|
import { FinalSpace } from "./finalSpace";
|
|
@@ -53,15 +54,10 @@ import { FinalSpace } from "./finalSpace";
|
|
|
53
54
|
* The version of IdCompressor that is currently persisted.
|
|
54
55
|
* This should not be changed without careful consideration to compatibility.
|
|
55
56
|
*/
|
|
56
|
-
const currentWrittenVersion =
|
|
57
|
+
const currentWrittenVersion = 2.0;
|
|
57
58
|
|
|
58
59
|
/**
|
|
59
60
|
* See {@link IIdCompressor} and {@link IIdCompressorCore}
|
|
60
|
-
*
|
|
61
|
-
* @alpha
|
|
62
|
-
*
|
|
63
|
-
* @deprecated Not intended for public use and will be removed from the public API in the future.
|
|
64
|
-
* Use `createIdCompressor` instead.
|
|
65
61
|
*/
|
|
66
62
|
export class IdCompressor implements IIdCompressor, IIdCompressorCore {
|
|
67
63
|
/**
|
|
@@ -103,10 +99,12 @@ export class IdCompressor implements IIdCompressor, IIdCompressorCore {
|
|
|
103
99
|
private telemetryLocalIdCount = 0;
|
|
104
100
|
// The number of eager final IDs generated since the last telemetry was sent.
|
|
105
101
|
private telemetryEagerFinalIdCount = 0;
|
|
102
|
+
// The ongoing ghost session, if one exists.
|
|
103
|
+
private ongoingGhostSession?: { cluster?: IdCluster; ghostSessionId: SessionId };
|
|
106
104
|
|
|
107
105
|
// #endregion
|
|
108
106
|
|
|
109
|
-
|
|
107
|
+
public constructor(
|
|
110
108
|
localSessionIdOrDeserialized: SessionId | Sessions,
|
|
111
109
|
private readonly logger?: ITelemetryLoggerExt,
|
|
112
110
|
) {
|
|
@@ -127,64 +125,57 @@ export class IdCompressor implements IIdCompressor, IIdCompressorCore {
|
|
|
127
125
|
}
|
|
128
126
|
}
|
|
129
127
|
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
sessionId: SessionId,
|
|
139
|
-
logger?: ITelemetryBaseLogger,
|
|
140
|
-
): IIdCompressor & IIdCompressorCore;
|
|
141
|
-
public static create(
|
|
142
|
-
sessionIdOrLogger?: SessionId | ITelemetryBaseLogger,
|
|
143
|
-
loggerOrUndefined?: ITelemetryBaseLogger,
|
|
144
|
-
): IIdCompressor & IIdCompressorCore {
|
|
145
|
-
let localSessionId: SessionId;
|
|
146
|
-
let logger: ITelemetryBaseLogger | undefined;
|
|
147
|
-
if (sessionIdOrLogger === undefined) {
|
|
148
|
-
localSessionId = createSessionId();
|
|
149
|
-
} else {
|
|
150
|
-
if (typeof sessionIdOrLogger === "string") {
|
|
151
|
-
localSessionId = sessionIdOrLogger;
|
|
152
|
-
logger = loggerOrUndefined;
|
|
128
|
+
public generateCompressedId(): SessionSpaceCompressedId {
|
|
129
|
+
// This ghost session code inside this block should not be changed without a version bump (it is performed at a consensus point)
|
|
130
|
+
if (this.ongoingGhostSession) {
|
|
131
|
+
if (this.ongoingGhostSession.cluster === undefined) {
|
|
132
|
+
this.ongoingGhostSession.cluster = this.addEmptyCluster(
|
|
133
|
+
this.sessions.getOrCreate(this.ongoingGhostSession.ghostSessionId),
|
|
134
|
+
1,
|
|
135
|
+
);
|
|
153
136
|
} else {
|
|
154
|
-
|
|
155
|
-
|
|
137
|
+
this.ongoingGhostSession.cluster.capacity++;
|
|
138
|
+
}
|
|
139
|
+
this.ongoingGhostSession.cluster.count++;
|
|
140
|
+
return lastFinalizedFinal(
|
|
141
|
+
this.ongoingGhostSession.cluster,
|
|
142
|
+
) as unknown as SessionSpaceCompressedId;
|
|
143
|
+
} else {
|
|
144
|
+
this.localGenCount++;
|
|
145
|
+
const lastCluster = this.localSession.getLastCluster();
|
|
146
|
+
if (lastCluster === undefined) {
|
|
147
|
+
this.telemetryLocalIdCount++;
|
|
148
|
+
return this.generateNextLocalId();
|
|
156
149
|
}
|
|
157
|
-
}
|
|
158
|
-
const compressor = new IdCompressor(
|
|
159
|
-
localSessionId,
|
|
160
|
-
logger === undefined ? undefined : createChildLogger({ logger }),
|
|
161
|
-
);
|
|
162
|
-
return compressor;
|
|
163
|
-
}
|
|
164
150
|
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
151
|
+
// If there exists a cluster of final IDs already claimed by the local session that still has room in it,
|
|
152
|
+
// it is known prior to range sequencing what a local ID's corresponding final ID will be.
|
|
153
|
+
// In this case, it is safe to return the final ID immediately. This is guaranteed to be safe because
|
|
154
|
+
// any op that the local session sends that contains one of those final IDs are guaranteed to arrive to
|
|
155
|
+
// collaborators *after* the one containing the creation range.
|
|
156
|
+
const clusterOffset = this.localGenCount - genCountFromLocalId(lastCluster.baseLocalId);
|
|
157
|
+
if (lastCluster.capacity > clusterOffset) {
|
|
158
|
+
this.telemetryEagerFinalIdCount++;
|
|
159
|
+
// Space in the cluster: eager final
|
|
160
|
+
return ((lastCluster.baseFinalId as number) +
|
|
161
|
+
clusterOffset) as SessionSpaceCompressedId;
|
|
162
|
+
}
|
|
163
|
+
// No space in the cluster, return next local
|
|
169
164
|
this.telemetryLocalIdCount++;
|
|
170
165
|
return this.generateNextLocalId();
|
|
171
166
|
}
|
|
167
|
+
}
|
|
172
168
|
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
return ((lastCluster.baseFinalId as number) +
|
|
183
|
-
clusterOffset) as SessionSpaceCompressedId;
|
|
169
|
+
/**
|
|
170
|
+
* {@inheritdoc IIdCompressorCore.beginGhostSession}
|
|
171
|
+
*/
|
|
172
|
+
public beginGhostSession(ghostSessionId: SessionId, ghostSessionCallback: () => void) {
|
|
173
|
+
this.ongoingGhostSession = { ghostSessionId };
|
|
174
|
+
try {
|
|
175
|
+
ghostSessionCallback();
|
|
176
|
+
} finally {
|
|
177
|
+
this.ongoingGhostSession = undefined;
|
|
184
178
|
}
|
|
185
|
-
// No space in the cluster, return next local
|
|
186
|
-
this.telemetryLocalIdCount++;
|
|
187
|
-
return this.generateNextLocalId();
|
|
188
179
|
}
|
|
189
180
|
|
|
190
181
|
private generateNextLocalId(): LocalCompressedId {
|
|
@@ -194,6 +185,10 @@ export class IdCompressor implements IIdCompressor, IIdCompressorCore {
|
|
|
194
185
|
}
|
|
195
186
|
|
|
196
187
|
public takeNextCreationRange(): IdCreationRange {
|
|
188
|
+
assert(
|
|
189
|
+
!this.ongoingGhostSession,
|
|
190
|
+
0x8a6 /* IdCompressor should not be operated normally when in a ghost session */,
|
|
191
|
+
);
|
|
197
192
|
const count = this.localGenCount - (this.nextRangeBaseGenCount - 1);
|
|
198
193
|
if (count === 0) {
|
|
199
194
|
return {
|
|
@@ -227,6 +222,10 @@ export class IdCompressor implements IIdCompressor, IIdCompressorCore {
|
|
|
227
222
|
}
|
|
228
223
|
|
|
229
224
|
public finalizeCreationRange(range: IdCreationRange): void {
|
|
225
|
+
assert(
|
|
226
|
+
!this.ongoingGhostSession,
|
|
227
|
+
0x8a7 /* IdCompressor should not be operated normally when in a ghost session */,
|
|
228
|
+
);
|
|
230
229
|
// Check if the range has IDs
|
|
231
230
|
if (range.ids === undefined) {
|
|
232
231
|
return;
|
|
@@ -310,6 +309,10 @@ export class IdCompressor implements IIdCompressor, IIdCompressorCore {
|
|
|
310
309
|
}
|
|
311
310
|
|
|
312
311
|
private addEmptyCluster(session: Session, capacity: number): IdCluster {
|
|
312
|
+
assert(
|
|
313
|
+
!this.ongoingGhostSession?.cluster,
|
|
314
|
+
0x8a8 /* IdCompressor should not be operated normally when in a ghost session */,
|
|
315
|
+
);
|
|
313
316
|
const newCluster = session.addNewCluster(
|
|
314
317
|
this.finalSpace.getAllocatedIdLimit(),
|
|
315
318
|
capacity,
|
|
@@ -473,6 +476,10 @@ export class IdCompressor implements IIdCompressor, IIdCompressorCore {
|
|
|
473
476
|
public serialize(withSession: true): SerializedIdCompressorWithOngoingSession;
|
|
474
477
|
public serialize(withSession: false): SerializedIdCompressorWithNoSession;
|
|
475
478
|
public serialize(hasLocalState: boolean): SerializedIdCompressor {
|
|
479
|
+
assert(
|
|
480
|
+
!this.ongoingGhostSession,
|
|
481
|
+
0x8a9 /* IdCompressor should not be operated normally when in a ghost session */,
|
|
482
|
+
);
|
|
476
483
|
const { normalizer, finalSpace, sessions } = this;
|
|
477
484
|
const sessionIndexMap = new Map<Session, number>();
|
|
478
485
|
let sessionIndex = 0;
|
|
@@ -542,15 +549,27 @@ export class IdCompressor implements IIdCompressor, IIdCompressorCore {
|
|
|
542
549
|
return bufferToString(serializedFloat.buffer, "base64") as SerializedIdCompressor;
|
|
543
550
|
}
|
|
544
551
|
|
|
545
|
-
public static deserialize(
|
|
552
|
+
public static deserialize(
|
|
553
|
+
serialized: SerializedIdCompressorWithOngoingSession,
|
|
554
|
+
logger?: ITelemetryLoggerExt,
|
|
555
|
+
): IdCompressor;
|
|
546
556
|
public static deserialize(
|
|
547
557
|
serialized: SerializedIdCompressorWithNoSession,
|
|
548
558
|
newSessionId: SessionId,
|
|
559
|
+
logger?: ITelemetryLoggerExt,
|
|
549
560
|
): IdCompressor;
|
|
550
561
|
public static deserialize(
|
|
551
562
|
serialized: SerializedIdCompressor,
|
|
552
|
-
|
|
563
|
+
loggerOrSessionId?: ITelemetryLoggerExt | SessionId,
|
|
553
564
|
): IdCompressor {
|
|
565
|
+
let sessionId: SessionId | undefined;
|
|
566
|
+
let logger: ITelemetryLoggerExt | undefined;
|
|
567
|
+
if (typeof loggerOrSessionId === "string") {
|
|
568
|
+
sessionId = loggerOrSessionId;
|
|
569
|
+
} else {
|
|
570
|
+
logger = loggerOrSessionId;
|
|
571
|
+
}
|
|
572
|
+
|
|
554
573
|
const buffer = stringToBuffer(serialized, "base64");
|
|
555
574
|
const index: Index = {
|
|
556
575
|
index: 0,
|
|
@@ -558,7 +577,21 @@ export class IdCompressor implements IIdCompressor, IIdCompressorCore {
|
|
|
558
577
|
bufferUint: new BigUint64Array(buffer),
|
|
559
578
|
};
|
|
560
579
|
const version = readNumber(index);
|
|
561
|
-
|
|
580
|
+
switch (version) {
|
|
581
|
+
case 1.0:
|
|
582
|
+
throw new Error("IdCompressor version 1.0 is no longer supported.");
|
|
583
|
+
case 2.0:
|
|
584
|
+
return IdCompressor.deserialize2_0(index, sessionId, logger);
|
|
585
|
+
default:
|
|
586
|
+
throw new Error("Unknown IdCompressor serialized version.");
|
|
587
|
+
}
|
|
588
|
+
}
|
|
589
|
+
|
|
590
|
+
static deserialize2_0(
|
|
591
|
+
index: Index,
|
|
592
|
+
sessionId?: SessionId,
|
|
593
|
+
logger?: ITelemetryLoggerExt,
|
|
594
|
+
): IdCompressor {
|
|
562
595
|
const hasLocalState = readBoolean(index);
|
|
563
596
|
const sessionCount = readNumber(index);
|
|
564
597
|
const clusterCount = readNumber(index);
|
|
@@ -584,7 +617,7 @@ export class IdCompressor implements IIdCompressor, IIdCompressorCore {
|
|
|
584
617
|
sessions.push([numeric, new Session(numeric)]);
|
|
585
618
|
}
|
|
586
619
|
|
|
587
|
-
const compressor = new IdCompressor(new Sessions(sessions));
|
|
620
|
+
const compressor = new IdCompressor(new Sessions(sessions), logger);
|
|
588
621
|
|
|
589
622
|
// Clusters
|
|
590
623
|
let baseFinalId = 0;
|
|
@@ -657,7 +690,24 @@ export function createIdCompressor(
|
|
|
657
690
|
sessionIdOrLogger?: SessionId | ITelemetryBaseLogger,
|
|
658
691
|
loggerOrUndefined?: ITelemetryBaseLogger,
|
|
659
692
|
): IIdCompressor & IIdCompressorCore {
|
|
660
|
-
|
|
693
|
+
let localSessionId: SessionId;
|
|
694
|
+
let logger: ITelemetryBaseLogger | undefined;
|
|
695
|
+
if (sessionIdOrLogger === undefined) {
|
|
696
|
+
localSessionId = createSessionId();
|
|
697
|
+
} else {
|
|
698
|
+
if (typeof sessionIdOrLogger === "string") {
|
|
699
|
+
localSessionId = sessionIdOrLogger;
|
|
700
|
+
logger = loggerOrUndefined;
|
|
701
|
+
} else {
|
|
702
|
+
localSessionId = createSessionId();
|
|
703
|
+
logger = sessionIdOrLogger;
|
|
704
|
+
}
|
|
705
|
+
}
|
|
706
|
+
const compressor = new IdCompressor(
|
|
707
|
+
localSessionId,
|
|
708
|
+
logger === undefined ? undefined : createChildLogger({ logger }),
|
|
709
|
+
);
|
|
710
|
+
return compressor;
|
|
661
711
|
}
|
|
662
712
|
|
|
663
713
|
/**
|
|
@@ -666,6 +716,7 @@ export function createIdCompressor(
|
|
|
666
716
|
*/
|
|
667
717
|
export function deserializeIdCompressor(
|
|
668
718
|
serialized: SerializedIdCompressorWithOngoingSession,
|
|
719
|
+
logger?: ITelemetryLoggerExt,
|
|
669
720
|
): IIdCompressor & IIdCompressorCore;
|
|
670
721
|
/**
|
|
671
722
|
* Deserializes the supplied state into an ID compressor.
|
|
@@ -674,12 +725,20 @@ export function deserializeIdCompressor(
|
|
|
674
725
|
export function deserializeIdCompressor(
|
|
675
726
|
serialized: SerializedIdCompressorWithNoSession,
|
|
676
727
|
newSessionId: SessionId,
|
|
728
|
+
logger?: ITelemetryLoggerExt,
|
|
677
729
|
): IIdCompressor & IIdCompressorCore;
|
|
678
730
|
export function deserializeIdCompressor(
|
|
679
731
|
serialized: SerializedIdCompressor | SerializedIdCompressorWithNoSession,
|
|
680
|
-
|
|
732
|
+
sessionIdOrLogger?: SessionId | ITelemetryLoggerExt,
|
|
733
|
+
logger?: ITelemetryLoggerExt | undefined,
|
|
681
734
|
): IIdCompressor & IIdCompressorCore {
|
|
682
|
-
|
|
683
|
-
|
|
684
|
-
|
|
735
|
+
if (typeof sessionIdOrLogger === "string") {
|
|
736
|
+
return IdCompressor.deserialize(
|
|
737
|
+
serialized as SerializedIdCompressorWithNoSession,
|
|
738
|
+
sessionIdOrLogger,
|
|
739
|
+
logger,
|
|
740
|
+
);
|
|
741
|
+
}
|
|
742
|
+
|
|
743
|
+
return IdCompressor.deserialize(serialized as SerializedIdCompressorWithOngoingSession, logger);
|
|
685
744
|
}
|
package/src/index.ts
CHANGED
|
@@ -7,7 +7,7 @@
|
|
|
7
7
|
* Exports for `id-compressor`
|
|
8
8
|
*/
|
|
9
9
|
|
|
10
|
-
export { createIdCompressor, deserializeIdCompressor
|
|
10
|
+
export { createIdCompressor, deserializeIdCompressor } from "./idCompressor";
|
|
11
11
|
export { createSessionId, assertIsStableId, generateStableId, isStableId } from "./utilities";
|
|
12
12
|
export {
|
|
13
13
|
IIdCompressorCore,
|
package/src/packageVersion.ts
CHANGED
package/src/sessions.ts
CHANGED
|
@@ -28,6 +28,28 @@ export interface IIdCompressorCore {
|
|
|
28
28
|
*/
|
|
29
29
|
finalizeCreationRange(range: IdCreationRange): void;
|
|
30
30
|
|
|
31
|
+
/**
|
|
32
|
+
* Run a callback that is performed from the perspective of a special "ghost" session.
|
|
33
|
+
* Any ids generated by this session will be immediately finalized on the local client as if
|
|
34
|
+
* they were created by a remote client with `ghostSessionId`.
|
|
35
|
+
*
|
|
36
|
+
* *WARNING:* This API requires an external consensus mechanism to safely use:
|
|
37
|
+
* In an attached container (i.e. multiple clients may have the document loaded), all clients must guarantee that:
|
|
38
|
+
* - They invoke this API starting from the same finalized creation ranges
|
|
39
|
+
* - This API is invoked with the same ghost session id
|
|
40
|
+
* - `ghostSessionCallback` deterministically mints the same number of ids on each client within the ghost session
|
|
41
|
+
* Failure to meet these requirement will result in divergence across clients and eventual consistency errors.
|
|
42
|
+
* While the ghost sesion callback is running, IdCompressor does not support serialization.
|
|
43
|
+
* @remarks This API is primarily intended for data migration scenarios which are able to deterministically transform
|
|
44
|
+
* data in some format into data in a new format.
|
|
45
|
+
* The first requirement (that all clients must invoke the API with the same finalized creation ranges) is guaranteed
|
|
46
|
+
* for this scenario because the data transformation callback occurs at a specific ack within the op stream on all
|
|
47
|
+
* clients, and clients don't finalize creation ranges for local changes they might have at this point in time.
|
|
48
|
+
* @param ghostSessionId - The session id that minted ids generated within `ghostSessionCallback` should be attributed to.
|
|
49
|
+
* @param ghostSessionCallback - Callback which mints ids attributed to the ghost session.
|
|
50
|
+
*/
|
|
51
|
+
beginGhostSession(ghostSessionId: SessionId, ghostSessionCallback: () => void);
|
|
52
|
+
|
|
31
53
|
/**
|
|
32
54
|
* Returns a persistable form of the current state of this `IdCompressor` which can be rehydrated via `IdCompressor.deserialize()`.
|
|
33
55
|
* This includes finalized state as well as un-finalized state and is therefore suitable for use in offline scenarios.
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"appendOnlySortedMap.cjs","sourceRoot":"","sources":["../src/appendOnlySortedMap.ts"],"names":[],"mappings":"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","sourcesContent":["/*!\n * Copyright (c) Microsoft Corporation and contributors. All rights reserved.\n * Licensed under the MIT License.\n */\n\n/* eslint-disable tsdoc/syntax */\n/* eslint-disable no-bitwise */\nimport { assert } from \"@fluidframework/core-utils\";\n\n/**\n * A map in which entries are always added in key-sorted order.\n * Supports appending and searching.\n */\nexport class AppendOnlySortedMap<K, V> {\n\tprotected readonly elements: (K | V)[] = [];\n\n\t/**\n\t * @param comparator - a comparator for keys\n\t */\n\tpublic constructor(protected readonly comparator: (a: K, b: K) => number) {}\n\n\t/**\n\t * @returns the number of entries in this map\n\t */\n\tpublic get size(): number {\n\t\treturn this.elements.length / 2;\n\t}\n\n\t/**\n\t * @returns the min key in the map.\n\t */\n\tpublic minKey(): K | undefined {\n\t\treturn this.elements[0] as K | undefined;\n\t}\n\n\t/**\n\t * @returns the max key in the map.\n\t */\n\tpublic maxKey(): K | undefined {\n\t\treturn this.elements[this.elements.length - 2] as K | undefined;\n\t}\n\n\t/**\n\t * @returns the min value in the map.\n\t */\n\tpublic minValue(): V | undefined {\n\t\treturn this.elements[1] as V | undefined;\n\t}\n\n\t/**\n\t * @returns the min value in the map.\n\t */\n\tpublic maxValue(): V | undefined {\n\t\treturn this.elements[this.elements.length - 1] as V | undefined;\n\t}\n\n\t/**\n\t * @returns the min key in the map.\n\t */\n\tpublic first(): [K, V] | undefined {\n\t\tconst { elements } = this;\n\t\tconst { length } = elements;\n\t\tif (length === 0) {\n\t\t\treturn undefined;\n\t\t}\n\t\treturn [elements[0] as K, elements[1] as V];\n\t}\n\n\t/**\n\t * @returns the max key in the map.\n\t */\n\tpublic last(): [K, V] | undefined {\n\t\tconst { elements } = this;\n\t\tconst { length } = elements;\n\t\tif (length === 0) {\n\t\t\treturn undefined;\n\t\t}\n\t\tconst lastKeyIndex = length - 2;\n\t\treturn [elements[lastKeyIndex] as K, elements[lastKeyIndex + 1] as V];\n\t}\n\n\t/**\n\t * Returns the element at the insertion index.\n\t */\n\tpublic getAtIndex(index: number): [K, V] | undefined {\n\t\tconst realIndex = index * 2;\n\t\tconst { elements } = this;\n\t\tif (realIndex < 0 || realIndex > elements.length - 1) {\n\t\t\treturn undefined;\n\t\t}\n\t\treturn [elements[realIndex] as K, elements[realIndex + 1] as V];\n\t}\n\n\t/**\n\t * @returns an iterable of the entries in the map.\n\t */\n\tpublic *entries(): IterableIterator<readonly [K, V]> {\n\t\tconst { elements } = this;\n\t\tfor (let i = 0; i < elements.length; i += 2) {\n\t\t\tyield [elements[i] as K, elements[i + 1] as V];\n\t\t}\n\t}\n\n\t/**\n\t * @returns an iterable of the keys in the map.\n\t */\n\tpublic *keys(): IterableIterator<K> {\n\t\tconst { elements } = this;\n\t\tfor (let i = 0; i < elements.length; i += 2) {\n\t\t\tyield elements[i] as K;\n\t\t}\n\t}\n\n\t/**\n\t * @returns an iterable of the values in the map.\n\t */\n\tpublic *values(): IterableIterator<V> {\n\t\tconst { elements } = this;\n\t\tfor (let i = 0; i < elements.length; i += 2) {\n\t\t\tyield elements[i + 1] as V;\n\t\t}\n\t}\n\n\t/**\n\t * @returns an iterable of the entries in the map, reversed.\n\t */\n\tpublic *entriesReversed(): IterableIterator<readonly [K, V]> {\n\t\tconst { elements } = this;\n\t\tfor (let i = elements.length - 2; i >= 0; i -= 2) {\n\t\t\tyield [elements[i] as K, elements[i + 1] as V];\n\t\t}\n\t}\n\n\t/**\n\t * Adds a new key/value pair to the map. `key` must be > to all keys in the map.\n\t * @param key - the key to add.\n\t * @param value - the value to add.\n\t */\n\tpublic append(key: K, value: V): void {\n\t\tconst { elements } = this;\n\t\tconst { length } = elements;\n\t\tif (length !== 0 && this.comparator(key, this.maxKey() as K) <= 0) {\n\t\t\tthrow new Error(\"Inserted key must be > all others in the map.\");\n\t\t}\n\t\telements.push(key);\n\t\telements.push(value);\n\t}\n\n\t/**\n\t * Replaces the last key/value pair with the given one. If the map is empty, it simply appends.\n\t * `key` must be > to all keys in the map prior to the one replaced.\n\t * @param key - the key to add.\n\t * @param value - the value to add.\n\t */\n\tpublic replaceLast(key: K, value: V): void {\n\t\tconst { elements, comparator } = this;\n\t\tconst { length } = elements;\n\t\tif (length !== 0) {\n\t\t\telements.pop();\n\t\t\telements.pop();\n\t\t\tif (comparator(key, this.maxKey() as K) <= 0) {\n\t\t\t\tthrow new Error(\"Inserted key must be > all others in the map.\");\n\t\t\t}\n\t\t}\n\t\telements.push(key);\n\t\telements.push(value);\n\t}\n\n\t/**\n\t * @param key - the key to lookup.\n\t * @returns the value associated with `key` if such an entry exists, and undefined otherwise.\n\t */\n\tpublic get(key: K): V | undefined {\n\t\tconst index = AppendOnlySortedMap.keyIndexOf(this.elements, key, this.comparator);\n\t\tif (index < 0) {\n\t\t\treturn undefined;\n\t\t}\n\t\treturn this.elements[index + 1] as V;\n\t}\n\n\t/**\n\t * @param key - the key to lookup.\n\t * @returns the entry associated with `key` if such an entry exists, the entry associated with the next lower key if such an entry\n\t * exists, and undefined otherwise.\n\t */\n\tpublic getPairOrNextLower(key: K): readonly [K, V] | undefined {\n\t\treturn this.getPairOrNextLowerBy(key, this.comparator);\n\t}\n\n\t/**\n\t * @param key - the key to lookup.\n\t * @returns the entry associated with `key` if such an entry exists, the entry associated with the next higher key if such an entry\n\t * exists, and undefined otherwise.\n\t */\n\tpublic getPairOrNextHigher(key: K): readonly [K, V] | undefined {\n\t\treturn this.getPairOrNextHigherBy(key, this.comparator);\n\t}\n\n\t/**\n\t * Compares two `AppendOnlySortedMap`s.\n\t */\n\tpublic equals(\n\t\tother: AppendOnlySortedMap<K, V>,\n\t\tcompareValues: (a: V, b: V) => boolean,\n\t): boolean {\n\t\tif (other === this) {\n\t\t\treturn true;\n\t\t}\n\n\t\tif (this.elements.length !== other.elements.length) {\n\t\t\treturn false;\n\t\t}\n\n\t\tfor (let i = this.elements.length - 2; i >= 0; i -= 2) {\n\t\t\tconst keyThis = this.elements[i] as K;\n\t\t\tconst valueThis = this.elements[i + 1] as V;\n\t\t\tconst keyOther = other.elements[i] as K;\n\t\t\tconst valueOther = other.elements[i + 1] as V;\n\t\t\tif (this.comparator(keyThis, keyOther) !== 0) {\n\t\t\t\treturn false;\n\t\t\t}\n\t\t\tif (!compareValues(valueThis, valueOther)) {\n\t\t\t\treturn false;\n\t\t\t}\n\t\t}\n\n\t\treturn true;\n\t}\n\n\t/**\n\t * Test-only expensive assertions to check the internal validity of the data structure.\n\t */\n\tpublic assertValid(): void {\n\t\tlet prev: readonly [K, unknown] | undefined;\n\t\tfor (const kv of this.entries()) {\n\t\t\tif (prev !== undefined) {\n\t\t\t\tassert(\n\t\t\t\t\tthis.comparator(kv[0], prev[0]) > 0,\n\t\t\t\t\t0x752 /* Keys in map must be sorted. */,\n\t\t\t\t);\n\t\t\t}\n\t\t\tprev = kv;\n\t\t}\n\t}\n\n\t/**\n\t * Queries a range of entries.\n\t * @param from - the key to start the range query at, inclusive.\n\t * @param to - the key to end the range query at, inclusive.\n\t * @returns the range of entries.\n\t */\n\tpublic *getRange(from: K, to: K): IterableIterator<readonly [K, V]> {\n\t\tconst keyIndexFrom = this.getKeyIndexOfOrNextHigher(from, this.comparator);\n\t\tif (keyIndexFrom === undefined) {\n\t\t\treturn;\n\t\t}\n\n\t\tconst keyIndexTo = this.getKeyIndexOfOrNextLower(to, this.comparator);\n\t\tif (keyIndexTo === undefined) {\n\t\t\treturn;\n\t\t}\n\n\t\tfor (let i = keyIndexFrom; i <= keyIndexTo; i += 2) {\n\t\t\tyield [this.elements[i] as K, this.elements[i + 1] as V];\n\t\t}\n\t}\n\n\tprotected getPairOrNextLowerBy<T>(\n\t\tsearch: T,\n\t\tcomparator: (search: T, key: K, value: V) => number,\n\t): readonly [K, V] | undefined {\n\t\tconst keyIndex = this.getKeyIndexOfOrNextLower(search, comparator);\n\t\tif (keyIndex === undefined) {\n\t\t\treturn undefined;\n\t\t}\n\n\t\treturn [this.elements[keyIndex] as K, this.elements[keyIndex + 1] as V];\n\t}\n\n\tprivate getKeyIndexOfOrNextLower<T>(\n\t\tsearch: T,\n\t\tcomparator: (search: T, key: K, value: V) => number,\n\t): number | undefined {\n\t\tconst { elements } = this;\n\t\tif (elements.length === 0) {\n\t\t\treturn undefined;\n\t\t}\n\t\tlet keyIndex = AppendOnlySortedMap.keyIndexOf(elements, search, comparator);\n\t\tif (keyIndex < 0) {\n\t\t\tkeyIndex ^= AppendOnlySortedMap.failureXor;\n\t\t\tif (keyIndex > 0) {\n\t\t\t\treturn keyIndex - 2;\n\t\t\t}\n\t\t\treturn undefined;\n\t\t}\n\t\treturn keyIndex;\n\t}\n\n\tprotected getPairOrNextHigherBy<T>(\n\t\tsearch: T,\n\t\tcomparator: (search: T, key: K, value: V) => number,\n\t): readonly [K, V] | undefined {\n\t\tconst keyIndex = this.getKeyIndexOfOrNextHigher(search, comparator);\n\t\tif (keyIndex === undefined) {\n\t\t\treturn undefined;\n\t\t}\n\n\t\treturn [this.elements[keyIndex] as K, this.elements[keyIndex + 1] as V];\n\t}\n\n\tprivate getKeyIndexOfOrNextHigher<T>(\n\t\tsearch: T,\n\t\tcomparator: (search: T, key: K, value: V) => number,\n\t): number | undefined {\n\t\tconst { elements } = this;\n\t\tconst { length } = elements;\n\t\tif (length === 0) {\n\t\t\treturn undefined;\n\t\t}\n\t\tlet keyIndex = AppendOnlySortedMap.keyIndexOf(elements, search, comparator);\n\t\tif (keyIndex < 0) {\n\t\t\tkeyIndex ^= AppendOnlySortedMap.failureXor;\n\t\t\tif (keyIndex < length) {\n\t\t\t\treturn keyIndex;\n\t\t\t}\n\t\t\treturn undefined;\n\t\t}\n\t\treturn keyIndex;\n\t}\n\n\t/**\n\t * The value xor'd with the result index when a search fails.\n\t */\n\tpublic static readonly failureXor = -1;\n\n\t/**\n\t * Performs a binary search on the sorted array.\n\t * @returns the index of the key for `search`, or (if not present) the index it would have been inserted into xor'd\n\t * with `failureXor`. Note that negating is not an adequate solution as that could result in -0.\n\t */\n\tpublic static keyIndexOf<T, K, V>(\n\t\telements: readonly (K | V)[],\n\t\tsearch: T,\n\t\tcomparator: (search: T, key: K, value: V) => number,\n\t): number {\n\t\t// Low, high, and mid are addresses of [K,V] pairs and *not* key indices\n\t\tlet low = 0;\n\t\tlet high = elements.length / 2;\n\t\tlet mid = high >> 1;\n\t\twhile (low < high) {\n\t\t\tconst keyIndex = mid * 2;\n\t\t\tconst c = comparator(search, elements[keyIndex] as K, elements[keyIndex + 1] as V);\n\t\t\tif (c > 0) {\n\t\t\t\tlow = mid + 1;\n\t\t\t} else if (c < 0) {\n\t\t\t\thigh = mid;\n\t\t\t} else if (c === 0) {\n\t\t\t\treturn keyIndex;\n\t\t\t} else {\n\t\t\t\tthrow new Error(\"Invalid comparator.\");\n\t\t\t}\n\t\t\tmid = (low + high) >> 1;\n\t\t}\n\t\treturn (mid * 2) ^ AppendOnlySortedMap.failureXor;\n\t}\n}\n"]}
|
package/dist/finalSpace.cjs.map
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"finalSpace.cjs","sourceRoot":"","sources":["../src/finalSpace.ts"],"names":[],"mappings":";AAAA;;;GAGG;;;AAEH,2DAAoD;AACpD,6CAA8F;AAG9F;;;;GAIG;AACH,MAAa,UAAU;IAAvB;QACkB,gBAAW,GAAgB,EAAE,CAAC;IAmDhD,CAAC;IAjDA,IAAW,QAAQ;QAClB,OAAO,IAAI,CAAC,WAAW,CAAC;IACzB,CAAC;IAEM,cAAc;QACpB,OAAO,IAAI,CAAC,WAAW,CAAC,IAAI,CAAC,WAAW,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC;IACtD,CAAC;IAEM,UAAU,CAAC,UAAqB;QACtC,MAAM,WAAW,GAAG,IAAI,CAAC,cAAc,EAAE,CAAC;QAC1C,IAAA,mBAAM,EACL,WAAW,KAAK,SAAS;YACxB,qEAAqE;YACrE,UAAU,CAAC,WAAW,KAAK,WAAW,CAAC,WAAW,GAAG,WAAW,CAAC,QAAQ,EAC1E,KAAK,CAAC,oDAAoD,CAC1D,CAAC;QACF,IAAI,CAAC,WAAW,CAAC,IAAI,CAAC,UAAU,CAAC,CAAC;IACnC,CAAC;IAED;;;OAGG;IACI,mBAAmB;QACzB,MAAM,WAAW,GAAG,IAAI,CAAC,cAAc,EAAE,CAAC;QAC1C,OAAO,WAAW,KAAK,SAAS;YAC/B,CAAC,CAAE,CAAuB;YAC1B,CAAC,CAAE,CAAE,IAAA,6BAAkB,EAAC,WAAW,CAAY,GAAG,CAAC,CAAuB,CAAC;IAC7E,CAAC;IAED;;;OAGG;IACI,mBAAmB;QACzB,MAAM,WAAW,GAAG,IAAI,CAAC,cAAc,EAAE,CAAC;QAC1C,OAAO,WAAW,KAAK,SAAS;YAC/B,CAAC,CAAE,CAAuB;YAC1B,CAAC,CAAE,CAAE,IAAA,6BAAkB,EAAC,WAAW,CAAY,GAAG,CAAC,CAAuB,CAAC;IAC7E,CAAC;IAEM,MAAM,CAAC,KAAiB;QAC9B,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,IAAI,CAAC,WAAW,CAAC,MAAM,EAAE,CAAC,EAAE,EAAE;YACjD,IAAI,CAAC,IAAA,wBAAa,EAAC,IAAI,CAAC,WAAW,CAAC,CAAC,CAAC,EAAE,KAAK,CAAC,WAAW,CAAC,CAAC,CAAC,CAAC,EAAE;gBAC9D,OAAO,KAAK,CAAC;aACb;SACD;QACD,OAAO,IAAI,CAAC,WAAW,CAAC,MAAM,KAAK,KAAK,CAAC,WAAW,CAAC,MAAM,CAAC;IAC7D,CAAC;CACD;AApDD,gCAoDC","sourcesContent":["/*!\n * Copyright (c) Microsoft Corporation and contributors. All rights reserved.\n * Licensed under the MIT License.\n */\n\nimport { assert } from \"@fluidframework/core-utils\";\nimport { IdCluster, clustersEqual, lastAllocatedFinal, lastFinalizedFinal } from \"./sessions\";\nimport { FinalCompressedId } from \"./identifiers\";\n\n/**\n * All IDs that have been finalized (acked), grouped into clusters sorted by their base final IDs.\n * These clusters span the positive integer space and are not sparse, meaning a cluster's base final\n * ID will always be one greater than the last final ID in the previous cluster (or 0 if there is not one).\n */\nexport class FinalSpace {\n\tprivate readonly clusterList: IdCluster[] = [];\n\n\tpublic get clusters(): readonly IdCluster[] {\n\t\treturn this.clusterList;\n\t}\n\n\tpublic getLastCluster(): IdCluster | undefined {\n\t\treturn this.clusterList[this.clusterList.length - 1];\n\t}\n\n\tpublic addCluster(newCluster: IdCluster) {\n\t\tconst lastCluster = this.getLastCluster();\n\t\tassert(\n\t\t\tlastCluster === undefined ||\n\t\t\t\t// eslint-disable-next-line @typescript-eslint/restrict-plus-operands\n\t\t\t\tnewCluster.baseFinalId === lastCluster.baseFinalId + lastCluster.capacity,\n\t\t\t0x753 /* Cluster insert to final_space is out of order. */,\n\t\t);\n\t\tthis.clusterList.push(newCluster);\n\t}\n\n\t/**\n\t * @returns the upper bound (exclusive) of finalized IDs in final space, i.e. one greater than the last final ID in the last cluster.\n\t * Note: this does not include allocated but unfinalized space in clusters.\n\t */\n\tpublic getFinalizedIdLimit(): FinalCompressedId {\n\t\tconst lastCluster = this.getLastCluster();\n\t\treturn lastCluster === undefined\n\t\t\t? (0 as FinalCompressedId)\n\t\t\t: (((lastFinalizedFinal(lastCluster) as number) + 1) as FinalCompressedId);\n\t}\n\n\t/**\n\t * @returns the upper bound (exclusive) of allocated IDs in final space, i.e. one greater than the last final ID in the last cluster.\n\t * Note: this does includes all allocated IDs in clusters.\n\t */\n\tpublic getAllocatedIdLimit(): FinalCompressedId {\n\t\tconst lastCluster = this.getLastCluster();\n\t\treturn lastCluster === undefined\n\t\t\t? (0 as FinalCompressedId)\n\t\t\t: (((lastAllocatedFinal(lastCluster) as number) + 1) as FinalCompressedId);\n\t}\n\n\tpublic equals(other: FinalSpace): boolean {\n\t\tfor (let i = 0; i < this.clusterList.length; i++) {\n\t\t\tif (!clustersEqual(this.clusterList[i], other.clusterList[i])) {\n\t\t\t\treturn false;\n\t\t\t}\n\t\t}\n\t\treturn this.clusterList.length === other.clusterList.length;\n\t}\n}\n"]}
|