@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.
Files changed (89) hide show
  1. package/CHANGELOG.md +12 -0
  2. package/README.md +0 -6
  3. package/api-report/id-compressor.api.md +4 -39
  4. package/dist/{appendOnlySortedMap.cjs → appendOnlySortedMap.js} +1 -1
  5. package/dist/appendOnlySortedMap.js.map +1 -0
  6. package/dist/{finalSpace.cjs → finalSpace.js} +2 -2
  7. package/dist/finalSpace.js.map +1 -0
  8. package/dist/id-compressor-alpha.d.ts +24 -59
  9. package/dist/id-compressor-beta.d.ts +1 -2
  10. package/dist/id-compressor-public.d.ts +1 -2
  11. package/dist/id-compressor-untrimmed.d.ts +24 -59
  12. package/dist/idCompressor.d.ts +13 -17
  13. package/dist/idCompressor.d.ts.map +1 -1
  14. package/dist/{idCompressor.cjs → idCompressor.js} +94 -55
  15. package/dist/idCompressor.js.map +1 -0
  16. package/dist/{identifiers.cjs → identifiers.js} +1 -1
  17. package/dist/identifiers.js.map +1 -0
  18. package/dist/index.d.ts +1 -1
  19. package/dist/index.d.ts.map +1 -1
  20. package/dist/{index.cjs → index.js} +4 -5
  21. package/dist/index.js.map +1 -0
  22. package/dist/packageVersion.d.ts +1 -1
  23. package/dist/{packageVersion.cjs → packageVersion.js} +2 -2
  24. package/dist/packageVersion.js.map +1 -0
  25. package/dist/{persistanceUtilities.cjs → persistanceUtilities.js} +1 -1
  26. package/dist/persistanceUtilities.js.map +1 -0
  27. package/dist/{sessionSpaceNormalizer.cjs → sessionSpaceNormalizer.js} +3 -3
  28. package/dist/sessionSpaceNormalizer.js.map +1 -0
  29. package/dist/{sessions.cjs → sessions.js} +5 -8
  30. package/dist/sessions.js.map +1 -0
  31. package/dist/tsdoc-metadata.json +1 -1
  32. package/dist/types/idCompressor.d.ts +21 -0
  33. package/dist/types/idCompressor.d.ts.map +1 -1
  34. package/dist/types/{identifiers.cjs → idCompressor.js} +1 -1
  35. package/dist/types/idCompressor.js.map +1 -0
  36. package/dist/types/identifiers.js +7 -0
  37. package/dist/types/identifiers.js.map +1 -0
  38. package/dist/types/{persisted-types/0.0.1.cjs → index.js} +1 -1
  39. package/dist/types/index.js.map +1 -0
  40. package/dist/types/{index.cjs → persisted-types/0.0.1.js} +1 -1
  41. package/dist/types/persisted-types/0.0.1.js.map +1 -0
  42. package/dist/types/persisted-types/{index.cjs → index.js} +1 -1
  43. package/dist/types/persisted-types/index.js.map +1 -0
  44. package/dist/{utilities.cjs → utilities.js} +1 -1
  45. package/dist/utilities.js.map +1 -0
  46. package/lib/id-compressor-alpha.d.mts +24 -59
  47. package/lib/id-compressor-beta.d.mts +1 -2
  48. package/lib/id-compressor-public.d.mts +1 -2
  49. package/lib/id-compressor-untrimmed.d.mts +24 -59
  50. package/lib/idCompressor.d.mts +13 -17
  51. package/lib/idCompressor.d.mts.map +1 -1
  52. package/lib/idCompressor.mjs +88 -49
  53. package/lib/idCompressor.mjs.map +1 -1
  54. package/lib/index.d.mts +1 -1
  55. package/lib/index.d.mts.map +1 -1
  56. package/lib/index.mjs +1 -1
  57. package/lib/index.mjs.map +1 -1
  58. package/lib/packageVersion.d.mts +1 -1
  59. package/lib/packageVersion.mjs +1 -1
  60. package/lib/packageVersion.mjs.map +1 -1
  61. package/lib/sessions.mjs +1 -1
  62. package/lib/sessions.mjs.map +1 -1
  63. package/lib/types/idCompressor.d.mts +21 -0
  64. package/lib/types/idCompressor.d.mts.map +1 -1
  65. package/lib/types/idCompressor.mjs.map +1 -1
  66. package/package.json +16 -15
  67. package/src/idCompressor.ts +125 -66
  68. package/src/index.ts +1 -1
  69. package/src/packageVersion.ts +1 -1
  70. package/src/sessions.ts +1 -1
  71. package/src/types/idCompressor.ts +22 -0
  72. package/dist/appendOnlySortedMap.cjs.map +0 -1
  73. package/dist/finalSpace.cjs.map +0 -1
  74. package/dist/idCompressor.cjs.map +0 -1
  75. package/dist/identifiers.cjs.map +0 -1
  76. package/dist/index.cjs.map +0 -1
  77. package/dist/packageVersion.cjs.map +0 -1
  78. package/dist/persistanceUtilities.cjs.map +0 -1
  79. package/dist/sessionSpaceNormalizer.cjs.map +0 -1
  80. package/dist/sessions.cjs.map +0 -1
  81. package/dist/types/idCompressor.cjs +0 -7
  82. package/dist/types/idCompressor.cjs.map +0 -1
  83. package/dist/types/identifiers.cjs.map +0 -1
  84. package/dist/types/index.cjs.map +0 -1
  85. package/dist/types/persisted-types/0.0.1.cjs.map +0 -1
  86. package/dist/types/persisted-types/index.cjs.map +0 -1
  87. package/dist/utilities.cjs.map +0 -1
  88. package/tsc-multi.test.json +0 -4
  89. /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.225277",
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.cjs",
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.225277",
39
- "@fluidframework/core-interfaces": "2.0.0-dev-rc.1.0.0.225277",
40
- "@fluidframework/core-utils": "2.0.0-dev-rc.1.0.0.225277",
41
- "@fluidframework/telemetry-utils": "2.0.0-dev-rc.1.0.0.225277",
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.225277",
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-222379",
49
+ "@fluid-tools/build-cli": "^0.29.0",
50
50
  "@fluidframework/build-common": "^2.0.3",
51
- "@fluidframework/build-tools": "0.29.0-222379",
52
- "@fluidframework/eslint-config-fluid": "^3.1.0",
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.225277",
55
- "@microsoft/api-extractor": "^7.38.3",
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-multi --config ./tsc-multi.test.json",
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
- "tsc": "tsc-multi --config ../../../common/build/build-common/tsc-multi.cjs.json",
113
+ "test:snapshots:regen": "npm run test:mocha -- --snapshot",
114
+ "tsc": "tsc",
114
115
  "tsc:watch": "tsc --watch"
115
116
  }
116
117
  }
@@ -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 = 1;
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
- private constructor(
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
- * @deprecated Use `createIdCompressor` instead.
132
- */
133
- public static create(logger?: ITelemetryBaseLogger): IIdCompressor & IIdCompressorCore;
134
- /**
135
- * @deprecated Use `createIdCompressor` instead.
136
- */
137
- public static create(
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
- localSessionId = createSessionId();
155
- logger = loggerOrUndefined;
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
- public generateCompressedId(): SessionSpaceCompressedId {
166
- this.localGenCount++;
167
- const lastCluster = this.localSession.getLastCluster();
168
- if (lastCluster === undefined) {
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
- // If there exists a cluster of final IDs already claimed by the local session that still has room in it,
174
- // it is known prior to range sequencing what a local ID's corresponding final ID will be.
175
- // In this case, it is safe to return the final ID immediately. This is guaranteed to be safe because
176
- // any op that the local session sends that contains one of those final IDs are guaranteed to arrive to
177
- // collaborators *after* the one containing the creation range.
178
- const clusterOffset = this.localGenCount - genCountFromLocalId(lastCluster.baseLocalId);
179
- if (lastCluster.capacity > clusterOffset) {
180
- this.telemetryEagerFinalIdCount++;
181
- // Space in the cluster: eager final
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(serialized: SerializedIdCompressorWithOngoingSession): IdCompressor;
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
- sessionId?: SessionId,
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
- assert(version === currentWrittenVersion, 0x75c /* Unknown serialized version. */);
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
- return IdCompressor.create(sessionIdOrLogger as SessionId, loggerOrUndefined);
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
- sessionId?: SessionId,
732
+ sessionIdOrLogger?: SessionId | ITelemetryLoggerExt,
733
+ logger?: ITelemetryLoggerExt | undefined,
681
734
  ): IIdCompressor & IIdCompressorCore {
682
- return sessionId === undefined
683
- ? IdCompressor.deserialize(serialized as SerializedIdCompressorWithOngoingSession)
684
- : IdCompressor.deserialize(serialized as SerializedIdCompressorWithNoSession, sessionId);
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, IdCompressor } from "./idCompressor";
10
+ export { createIdCompressor, deserializeIdCompressor } from "./idCompressor";
11
11
  export { createSessionId, assertIsStableId, generateStableId, isStableId } from "./utilities";
12
12
  export {
13
13
  IIdCompressorCore,
@@ -6,4 +6,4 @@
6
6
  */
7
7
 
8
8
  export const pkgName = "@fluidframework/id-compressor";
9
- export const pkgVersion = "2.0.0-dev-rc.1.0.0.225277";
9
+ export const pkgVersion = "2.0.0-dev-rc.1.0.0.232845";
package/src/sessions.ts CHANGED
@@ -3,7 +3,7 @@
3
3
  * Licensed under the MIT License.
4
4
  */
5
5
 
6
- import BTree from "sorted-btree";
6
+ import { BTree } from "@tylerbu/sorted-btree-es6";
7
7
  import { assert } from "@fluidframework/core-utils";
8
8
  import { SessionId, StableId } from "./types";
9
9
  import {
@@ -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"]}
@@ -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"]}