@fluidframework/id-compressor 2.0.0-dev-rc.1.0.0.224419 → 2.0.0-dev-rc.1.0.0.228517
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-extractor-esm.json +4 -0
- package/api-report/id-compressor.api.md +2 -0
- 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 +27 -0
- package/dist/id-compressor-untrimmed.d.ts +27 -0
- package/dist/idCompressor.d.ts +6 -0
- package/dist/idCompressor.d.ts.map +1 -1
- package/dist/{idCompressor.cjs → idCompressor.js} +65 -27
- 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.cjs → index.js} +3 -3
- 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/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/appendOnlySortedMap.d.mts +1 -1
- package/lib/appendOnlySortedMap.d.mts.map +1 -1
- package/lib/finalSpace.d.mts +1 -1
- package/lib/finalSpace.d.mts.map +1 -1
- package/lib/id-compressor-alpha.d.mts +27 -0
- package/lib/id-compressor-untrimmed.d.mts +27 -0
- package/lib/idCompressor.d.mts +7 -1
- package/lib/idCompressor.d.mts.map +1 -1
- package/lib/idCompressor.mjs +59 -21
- package/lib/idCompressor.mjs.map +1 -1
- package/lib/identifiers.d.mts +1 -1
- package/lib/identifiers.d.mts.map +1 -1
- package/lib/index.d.mts +1 -1
- package/lib/index.d.mts.map +1 -1
- package/lib/packageVersion.d.mts +2 -2
- package/lib/packageVersion.d.mts.map +1 -1
- package/lib/packageVersion.mjs +1 -1
- package/lib/packageVersion.mjs.map +1 -1
- package/lib/persistanceUtilities.d.mts +1 -1
- package/lib/persistanceUtilities.d.mts.map +1 -1
- package/lib/sessionSpaceNormalizer.d.mts +1 -1
- package/lib/sessionSpaceNormalizer.d.mts.map +1 -1
- package/lib/sessions.d.mts +1 -1
- package/lib/sessions.d.mts.map +1 -1
- package/lib/sessions.mjs +1 -1
- package/lib/sessions.mjs.map +1 -1
- package/lib/types/idCompressor.d.mts +22 -1
- package/lib/types/idCompressor.d.mts.map +1 -1
- package/lib/types/idCompressor.mjs.map +1 -1
- package/lib/types/identifiers.d.mts +1 -1
- package/lib/types/identifiers.d.mts.map +1 -1
- package/lib/types/index.d.mts +1 -1
- package/lib/types/index.d.mts.map +1 -1
- package/lib/types/persisted-types/0.0.1.d.mts +1 -1
- package/lib/types/persisted-types/0.0.1.d.mts.map +1 -1
- package/lib/types/persisted-types/index.d.mts +1 -1
- package/lib/types/persisted-types/index.d.mts.map +1 -1
- package/lib/utilities.d.mts +1 -1
- package/lib/utilities.d.mts.map +1 -1
- package/package.json +20 -18
- package/src/idCompressor.ts +76 -19
- 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
|
@@ -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.
|
|
@@ -136,4 +157,4 @@ export interface IIdCompressor {
|
|
|
136
157
|
*/
|
|
137
158
|
tryRecompress(uncompressed: StableId): SessionSpaceCompressedId | undefined;
|
|
138
159
|
}
|
|
139
|
-
//# sourceMappingURL=idCompressor.d.
|
|
160
|
+
//# sourceMappingURL=idCompressor.d.mts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"idCompressor.d.
|
|
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"]}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"identifiers.d.
|
|
1
|
+
{"version":3,"file":"identifiers.d.mts","sourceRoot":"","sources":["../../src/types/identifiers.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH;;;;;GAKG;AACH,MAAM,MAAM,wBAAwB,GAAG,MAAM,GAAG;IAC/C,QAAQ,CAAC,aAAa,EAAE,sCAAsC,CAAC;CAC/D,CAAC;AAEF;;;;;GAKG;AACH,MAAM,MAAM,mBAAmB,GAAG,MAAM,GAAG;IAC1C,QAAQ,CAAC,YAAY,EAAE,sCAAsC,CAAC;CAC9D,CAAC;AAEF;;;;;;GAMG;AACH,MAAM,MAAM,QAAQ,GAAG,MAAM,GAAG;IAAE,QAAQ,CAAC,QAAQ,EAAE,sCAAsC,CAAA;CAAE,CAAC;AAE9F;;;GAGG;AACH,MAAM,MAAM,SAAS,GAAG,QAAQ,GAAG;IAAE,QAAQ,CAAC,SAAS,EAAE,sCAAsC,CAAA;CAAE,CAAC"}
|
package/lib/types/index.d.mts
CHANGED
|
@@ -5,4 +5,4 @@
|
|
|
5
5
|
export { IdCreationRange, SerializedIdCompressor, SerializedIdCompressorWithNoSession, SerializedIdCompressorWithOngoingSession, } from "./persisted-types/index.mjs";
|
|
6
6
|
export { IIdCompressorCore, IIdCompressor } from "./idCompressor.mjs";
|
|
7
7
|
export { SessionSpaceCompressedId, OpSpaceCompressedId, SessionId, StableId } from "./identifiers.mjs";
|
|
8
|
-
//# sourceMappingURL=index.d.
|
|
8
|
+
//# sourceMappingURL=index.d.mts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.
|
|
1
|
+
{"version":3,"file":"index.d.mts","sourceRoot":"","sources":["../../src/types/index.ts"],"names":[],"mappings":"AAAA;;;GAGG;OAEI,EACN,eAAe,EACf,sBAAsB,EACtB,mCAAmC,EACnC,wCAAwC,GACxC;OAEM,EAAE,iBAAiB,EAAE,aAAa,EAAE;OAEpC,EAAE,wBAAwB,EAAE,mBAAmB,EAAE,SAAS,EAAE,QAAQ,EAAE"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"0.0.1.d.
|
|
1
|
+
{"version":3,"file":"0.0.1.d.mts","sourceRoot":"","sources":["../../../src/types/persisted-types/0.0.1.ts"],"names":[],"mappings":"AAAA;;;GAGG;OAEI,KAAK,EAAE,SAAS,EAAE;AAEzB;;;GAGG;AACH,MAAM,MAAM,sBAAsB,GAAG,MAAM,GAAG;IAC7C,QAAQ,CAAC,uBAAuB,EAAE,sCAAsC,CAAC;CACzE,CAAC;AAEF;;;GAGG;AACH,MAAM,MAAM,mCAAmC,GAAG,sBAAsB,GAAG;IAC1E,QAAQ,CAAC,aAAa,EAAE,sCAAsC,CAAC;CAC/D,CAAC;AAEF;;;GAGG;AACH,MAAM,MAAM,wCAAwC,GAAG,sBAAsB,GAAG;IAC/E,QAAQ,CAAC,cAAc,EAAE,sCAAsC,CAAC;CAChE,CAAC;AAEF;;;;;GAKG;AACH,MAAM,WAAW,eAAe;IAC/B,QAAQ,CAAC,SAAS,EAAE,SAAS,CAAC;IAC9B,QAAQ,CAAC,GAAG,CAAC,EAAE;QACd;;WAEG;QACH,QAAQ,CAAC,aAAa,EAAE,MAAM,CAAC;QAE/B;;WAEG;QACH,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;QAEvB;;;;WAIG;QACH,QAAQ,CAAC,oBAAoB,EAAE,MAAM,CAAC;KACtC,CAAC;CACF"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.
|
|
1
|
+
{"version":3,"file":"index.d.mts","sourceRoot":"","sources":["../../../src/types/persisted-types/index.ts"],"names":[],"mappings":"AAAA;;;GAGG;OAEI,EACN,eAAe,EACf,sBAAsB,EACtB,mCAAmC,EACnC,wCAAwC,GACxC"}
|
package/lib/utilities.d.mts
CHANGED
|
@@ -50,4 +50,4 @@ export declare function stableIdFromNumericUuid(numericUuid: NumericUuid): Stabl
|
|
|
50
50
|
export declare function offsetNumericUuid(numericUuid: NumericUuid, offset: number): NumericUuid;
|
|
51
51
|
export declare function subtractNumericUuids(a: NumericUuid, b: NumericUuid): NumericUuid;
|
|
52
52
|
export declare function addNumericUuids(a: NumericUuid, b: NumericUuid): NumericUuid;
|
|
53
|
-
//# sourceMappingURL=utilities.d.
|
|
53
|
+
//# sourceMappingURL=utilities.d.mts.map
|
package/lib/utilities.d.mts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"utilities.d.
|
|
1
|
+
{"version":3,"file":"utilities.d.mts","sourceRoot":"","sources":["../src/utilities.ts"],"names":[],"mappings":"AAAA;;;GAGG;OAKI,EAAE,SAAS,EAAE,QAAQ,EAAE;OACvB,EAAE,iBAAiB,EAAE,WAAW,EAAE;AAmBzC;;;GAGG;AACH,wBAAgB,eAAe,IAAI,SAAS,CAE3C;AAED;;;GAGG;AACH,wBAAgB,gBAAgB,CAAC,QAAQ,EAAE,MAAM,GAAG,QAAQ,CAG3D;AAED;;GAEG;AACH,wBAAgB,iBAAiB,CAAC,QAAQ,EAAE,MAAM,GAAG,SAAS,CAG7D;AAED;;;GAGG;AACH,wBAAgB,gBAAgB,IAAI,QAAQ,CAE3C;AAED;;;;GAIG;AACH,wBAAgB,UAAU,CAAC,GAAG,EAAE,MAAM,GAAG,GAAG,IAAI,QAAQ,CAuCvD;AAED;;;;GAIG;AACH,wBAAgB,oBAAoB,CAAC,CAAC,SAAS,MAAM,EAAE,CAAC,EAAE,CAAC,EAAE,CAAC,EAAE,CAAC,GAAG,MAAM,CAEzE;AAED;;GAEG;AACH,wBAAgB,cAAc,CAAC,CAAC,SAAS,MAAM,EAAE,CAAC,EAAE,CAAC,EAAE,CAAC,EAAE,CAAC,GAAG,MAAM,CAEnE;AAED;;GAEG;AACH,wBAAgB,cAAc,CAAC,CAAC,SAAS,MAAM,EAAE,CAAC,EAAE,CAAC,EAAE,CAAC,EAAE,CAAC,GAAG,MAAM,CAEnE;AAED,wBAAgB,mBAAmB,CAAC,OAAO,EAAE,iBAAiB,GAAG,MAAM,CAEtE;AAED,wBAAgB,mBAAmB,CAAC,QAAQ,EAAE,MAAM,GAAG,iBAAiB,CAEvE;AAeD,wBAAgB,uBAAuB,CAAC,QAAQ,EAAE,QAAQ,GAAG,WAAW,CAWvE;AAED,wBAAgB,uBAAuB,CAAC,WAAW,EAAE,WAAW,GAAG,QAAQ,CAY1E;AAED,wBAAgB,iBAAiB,CAAC,WAAW,EAAE,WAAW,EAAE,MAAM,EAAE,MAAM,GAAG,WAAW,CAGvF;AAED,wBAAgB,oBAAoB,CAAC,CAAC,EAAE,WAAW,EAAE,CAAC,EAAE,WAAW,GAAG,WAAW,CAEhF;AAED,wBAAgB,eAAe,CAAC,CAAC,EAAE,WAAW,EAAE,CAAC,EAAE,WAAW,GAAG,WAAW,CAG3E"}
|
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.228517",
|
|
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,26 +35,27 @@
|
|
|
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.228517",
|
|
39
|
+
"@fluidframework/core-interfaces": "2.0.0-dev-rc.1.0.0.228517",
|
|
40
|
+
"@fluidframework/core-utils": "2.0.0-dev-rc.1.0.0.228517",
|
|
41
|
+
"@fluidframework/telemetry-utils": "2.0.0-dev-rc.1.0.0.228517",
|
|
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.228517",
|
|
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.
|
|
53
|
-
"@fluidframework/
|
|
51
|
+
"@fluidframework/build-tools": "^0.29.0",
|
|
52
|
+
"@fluidframework/eslint-config-fluid": "^3.2.0",
|
|
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.228517",
|
|
54
55
|
"@microsoft/api-extractor": "^7.38.3",
|
|
55
56
|
"@types/mocha": "^9.1.1",
|
|
56
57
|
"@types/uuid": "^9.0.2",
|
|
57
|
-
"c8": "^
|
|
58
|
+
"c8": "^8.0.1",
|
|
58
59
|
"copyfiles": "^2.4.1",
|
|
59
60
|
"cross-env": "^7.0.3",
|
|
60
61
|
"eslint": "~8.50.0",
|
|
@@ -63,7 +64,6 @@
|
|
|
63
64
|
"mocha-multi-reporters": "^1.5.1",
|
|
64
65
|
"moment": "^2.21.0",
|
|
65
66
|
"prettier": "~3.0.3",
|
|
66
|
-
"renamer": "^4.0.0",
|
|
67
67
|
"rimraf": "^4.4.0",
|
|
68
68
|
"tsc-multi": "^1.1.0",
|
|
69
69
|
"typescript": "~5.1.6"
|
|
@@ -80,18 +80,20 @@
|
|
|
80
80
|
}
|
|
81
81
|
}
|
|
82
82
|
},
|
|
83
|
+
"typeValidation": {
|
|
84
|
+
"broken": {}
|
|
85
|
+
},
|
|
83
86
|
"scripts": {
|
|
84
87
|
"api": "fluid-build . --task api",
|
|
85
88
|
"api-extractor:commonjs": "api-extractor run --local",
|
|
86
|
-
"api-extractor:esnext": "
|
|
89
|
+
"api-extractor:esnext": "api-extractor run --config ./api-extractor-esm.json",
|
|
87
90
|
"build": "fluid-build . --task build",
|
|
88
91
|
"build:commonjs": "fluid-build . --task commonjs",
|
|
89
92
|
"build:compile": "fluid-build . --task compile",
|
|
90
93
|
"build:docs": "fluid-build . --task api",
|
|
91
94
|
"build:esnext": "tsc-multi --config ../../../common/build/build-common/tsc-multi.esm.json",
|
|
92
95
|
"build:genver": "gen-version",
|
|
93
|
-
"build:
|
|
94
|
-
"build:test": "tsc-multi --config ./tsc-multi.test.json",
|
|
96
|
+
"build:test": "tsc --project ./src/test/tsconfig.json",
|
|
95
97
|
"check:are-the-types-wrong": "attw --pack",
|
|
96
98
|
"check:release-tags": "api-extractor run --local --config ./api-extractor-lint.json",
|
|
97
99
|
"ci:build:docs": "api-extractor run",
|
|
@@ -108,7 +110,7 @@
|
|
|
108
110
|
"test:coverage": "c8 npm test",
|
|
109
111
|
"test:mocha": "mocha --ignore \"dist/test/types/*\" --recursive dist/test -r node_modules/@fluidframework/mocha-test-setup",
|
|
110
112
|
"test:mocha:verbose": "cross-env FLUID_TEST_VERBOSE=1 npm run test:mocha",
|
|
111
|
-
"tsc": "tsc
|
|
113
|
+
"tsc": "tsc",
|
|
112
114
|
"tsc:watch": "tsc --watch"
|
|
113
115
|
}
|
|
114
116
|
}
|
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,7 +54,7 @@ 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}
|
|
@@ -103,6 +104,8 @@ export class IdCompressor implements IIdCompressor, IIdCompressorCore {
|
|
|
103
104
|
private telemetryLocalIdCount = 0;
|
|
104
105
|
// The number of eager final IDs generated since the last telemetry was sent.
|
|
105
106
|
private telemetryEagerFinalIdCount = 0;
|
|
107
|
+
// The ongoing ghost session, if one exists.
|
|
108
|
+
private ongoingGhostSession?: { cluster?: IdCluster; ghostSessionId: SessionId };
|
|
106
109
|
|
|
107
110
|
// #endregion
|
|
108
111
|
|
|
@@ -163,28 +166,56 @@ export class IdCompressor implements IIdCompressor, IIdCompressorCore {
|
|
|
163
166
|
}
|
|
164
167
|
|
|
165
168
|
public generateCompressedId(): SessionSpaceCompressedId {
|
|
166
|
-
this
|
|
167
|
-
|
|
168
|
-
|
|
169
|
+
// This ghost session code inside this block should not be changed without a version bump (it is performed at a consensus point)
|
|
170
|
+
if (this.ongoingGhostSession) {
|
|
171
|
+
if (this.ongoingGhostSession.cluster === undefined) {
|
|
172
|
+
this.ongoingGhostSession.cluster = this.addEmptyCluster(
|
|
173
|
+
this.sessions.getOrCreate(this.ongoingGhostSession.ghostSessionId),
|
|
174
|
+
1,
|
|
175
|
+
);
|
|
176
|
+
} else {
|
|
177
|
+
this.ongoingGhostSession.cluster.capacity++;
|
|
178
|
+
}
|
|
179
|
+
this.ongoingGhostSession.cluster.count++;
|
|
180
|
+
return lastFinalizedFinal(
|
|
181
|
+
this.ongoingGhostSession.cluster,
|
|
182
|
+
) as unknown as SessionSpaceCompressedId;
|
|
183
|
+
} else {
|
|
184
|
+
this.localGenCount++;
|
|
185
|
+
const lastCluster = this.localSession.getLastCluster();
|
|
186
|
+
if (lastCluster === undefined) {
|
|
187
|
+
this.telemetryLocalIdCount++;
|
|
188
|
+
return this.generateNextLocalId();
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
// If there exists a cluster of final IDs already claimed by the local session that still has room in it,
|
|
192
|
+
// it is known prior to range sequencing what a local ID's corresponding final ID will be.
|
|
193
|
+
// In this case, it is safe to return the final ID immediately. This is guaranteed to be safe because
|
|
194
|
+
// any op that the local session sends that contains one of those final IDs are guaranteed to arrive to
|
|
195
|
+
// collaborators *after* the one containing the creation range.
|
|
196
|
+
const clusterOffset = this.localGenCount - genCountFromLocalId(lastCluster.baseLocalId);
|
|
197
|
+
if (lastCluster.capacity > clusterOffset) {
|
|
198
|
+
this.telemetryEagerFinalIdCount++;
|
|
199
|
+
// Space in the cluster: eager final
|
|
200
|
+
return ((lastCluster.baseFinalId as number) +
|
|
201
|
+
clusterOffset) as SessionSpaceCompressedId;
|
|
202
|
+
}
|
|
203
|
+
// No space in the cluster, return next local
|
|
169
204
|
this.telemetryLocalIdCount++;
|
|
170
205
|
return this.generateNextLocalId();
|
|
171
206
|
}
|
|
207
|
+
}
|
|
172
208
|
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
return ((lastCluster.baseFinalId as number) +
|
|
183
|
-
clusterOffset) as SessionSpaceCompressedId;
|
|
209
|
+
/**
|
|
210
|
+
* {@inheritdoc IIdCompressorCore.beginGhostSession}
|
|
211
|
+
*/
|
|
212
|
+
public beginGhostSession(ghostSessionId: SessionId, ghostSessionCallback: () => void) {
|
|
213
|
+
this.ongoingGhostSession = { ghostSessionId };
|
|
214
|
+
try {
|
|
215
|
+
ghostSessionCallback();
|
|
216
|
+
} finally {
|
|
217
|
+
this.ongoingGhostSession = undefined;
|
|
184
218
|
}
|
|
185
|
-
// No space in the cluster, return next local
|
|
186
|
-
this.telemetryLocalIdCount++;
|
|
187
|
-
return this.generateNextLocalId();
|
|
188
219
|
}
|
|
189
220
|
|
|
190
221
|
private generateNextLocalId(): LocalCompressedId {
|
|
@@ -194,6 +225,10 @@ export class IdCompressor implements IIdCompressor, IIdCompressorCore {
|
|
|
194
225
|
}
|
|
195
226
|
|
|
196
227
|
public takeNextCreationRange(): IdCreationRange {
|
|
228
|
+
assert(
|
|
229
|
+
!this.ongoingGhostSession,
|
|
230
|
+
0x8a6 /* IdCompressor should not be operated normally when in a ghost session */,
|
|
231
|
+
);
|
|
197
232
|
const count = this.localGenCount - (this.nextRangeBaseGenCount - 1);
|
|
198
233
|
if (count === 0) {
|
|
199
234
|
return {
|
|
@@ -227,6 +262,10 @@ export class IdCompressor implements IIdCompressor, IIdCompressorCore {
|
|
|
227
262
|
}
|
|
228
263
|
|
|
229
264
|
public finalizeCreationRange(range: IdCreationRange): void {
|
|
265
|
+
assert(
|
|
266
|
+
!this.ongoingGhostSession,
|
|
267
|
+
0x8a7 /* IdCompressor should not be operated normally when in a ghost session */,
|
|
268
|
+
);
|
|
230
269
|
// Check if the range has IDs
|
|
231
270
|
if (range.ids === undefined) {
|
|
232
271
|
return;
|
|
@@ -310,6 +349,10 @@ export class IdCompressor implements IIdCompressor, IIdCompressorCore {
|
|
|
310
349
|
}
|
|
311
350
|
|
|
312
351
|
private addEmptyCluster(session: Session, capacity: number): IdCluster {
|
|
352
|
+
assert(
|
|
353
|
+
!this.ongoingGhostSession?.cluster,
|
|
354
|
+
0x8a8 /* IdCompressor should not be operated normally when in a ghost session */,
|
|
355
|
+
);
|
|
313
356
|
const newCluster = session.addNewCluster(
|
|
314
357
|
this.finalSpace.getAllocatedIdLimit(),
|
|
315
358
|
capacity,
|
|
@@ -473,6 +516,10 @@ export class IdCompressor implements IIdCompressor, IIdCompressorCore {
|
|
|
473
516
|
public serialize(withSession: true): SerializedIdCompressorWithOngoingSession;
|
|
474
517
|
public serialize(withSession: false): SerializedIdCompressorWithNoSession;
|
|
475
518
|
public serialize(hasLocalState: boolean): SerializedIdCompressor {
|
|
519
|
+
assert(
|
|
520
|
+
!this.ongoingGhostSession,
|
|
521
|
+
0x8a9 /* IdCompressor should not be operated normally when in a ghost session */,
|
|
522
|
+
);
|
|
476
523
|
const { normalizer, finalSpace, sessions } = this;
|
|
477
524
|
const sessionIndexMap = new Map<Session, number>();
|
|
478
525
|
let sessionIndex = 0;
|
|
@@ -558,7 +605,17 @@ export class IdCompressor implements IIdCompressor, IIdCompressorCore {
|
|
|
558
605
|
bufferUint: new BigUint64Array(buffer),
|
|
559
606
|
};
|
|
560
607
|
const version = readNumber(index);
|
|
561
|
-
|
|
608
|
+
switch (version) {
|
|
609
|
+
case 1.0:
|
|
610
|
+
throw new Error("IdCompressor version 1.0 is no longer supported.");
|
|
611
|
+
case 2.0:
|
|
612
|
+
return IdCompressor.deserialize2_0(index, sessionId);
|
|
613
|
+
default:
|
|
614
|
+
throw new Error("Unknown IdCompressor serialized version.");
|
|
615
|
+
}
|
|
616
|
+
}
|
|
617
|
+
|
|
618
|
+
private static deserialize2_0(index: Index, sessionId?: SessionId): IdCompressor {
|
|
562
619
|
const hasLocalState = readBoolean(index);
|
|
563
620
|
const sessionCount = readNumber(index);
|
|
564
621
|
const clusterCount = readNumber(index);
|
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"]}
|