@fluidframework/odsp-driver 3.2.0 → 3.3.0

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 (32) hide show
  1. package/CHANGELOG.md +4 -0
  2. package/dist/odspVersionManager/odspVersionManager.d.ts +8 -0
  3. package/dist/odspVersionManager/odspVersionManager.d.ts.map +1 -1
  4. package/dist/odspVersionManager/odspVersionManager.js +19 -5
  5. package/dist/odspVersionManager/odspVersionManager.js.map +1 -1
  6. package/dist/packageVersion.d.ts +1 -1
  7. package/dist/packageVersion.js +1 -1
  8. package/dist/packageVersion.js.map +1 -1
  9. package/dist/pointInTimeDriver/createPointInTimeDocumentService.d.ts.map +1 -1
  10. package/dist/pointInTimeDriver/createPointInTimeDocumentService.js +47 -5
  11. package/dist/pointInTimeDriver/createPointInTimeDocumentService.js.map +1 -1
  12. package/dist/pointInTimeDriver/odspPointInTimeDocumentService.d.ts.map +1 -1
  13. package/dist/pointInTimeDriver/odspPointInTimeDocumentService.js +40 -1
  14. package/dist/pointInTimeDriver/odspPointInTimeDocumentService.js.map +1 -1
  15. package/lib/odspVersionManager/odspVersionManager.d.ts +8 -0
  16. package/lib/odspVersionManager/odspVersionManager.d.ts.map +1 -1
  17. package/lib/odspVersionManager/odspVersionManager.js +19 -5
  18. package/lib/odspVersionManager/odspVersionManager.js.map +1 -1
  19. package/lib/packageVersion.d.ts +1 -1
  20. package/lib/packageVersion.js +1 -1
  21. package/lib/packageVersion.js.map +1 -1
  22. package/lib/pointInTimeDriver/createPointInTimeDocumentService.d.ts.map +1 -1
  23. package/lib/pointInTimeDriver/createPointInTimeDocumentService.js +48 -6
  24. package/lib/pointInTimeDriver/createPointInTimeDocumentService.js.map +1 -1
  25. package/lib/pointInTimeDriver/odspPointInTimeDocumentService.d.ts.map +1 -1
  26. package/lib/pointInTimeDriver/odspPointInTimeDocumentService.js +40 -1
  27. package/lib/pointInTimeDriver/odspPointInTimeDocumentService.js.map +1 -1
  28. package/package.json +12 -12
  29. package/src/odspVersionManager/odspVersionManager.ts +27 -7
  30. package/src/packageVersion.ts +1 -1
  31. package/src/pointInTimeDriver/createPointInTimeDocumentService.ts +59 -8
  32. package/src/pointInTimeDriver/odspPointInTimeDocumentService.ts +47 -8
package/CHANGELOG.md CHANGED
@@ -1,5 +1,9 @@
1
1
  # @fluidframework/odsp-driver
2
2
 
3
+ ## 3.3.0
4
+
5
+ Dependency updates only.
6
+
3
7
  ## 3.2.0
4
8
 
5
9
  Dependency updates only.
@@ -27,6 +27,10 @@ export type BaseForSeq = {
27
27
  /** A recoverable version with `sequenceNumber <= target` was found. */
28
28
  readonly kind: "found";
29
29
  readonly base: ResolvedVersion;
30
+ /** Number of sealed version candidates examined before selecting the base. */
31
+ readonly versionsProbed: number;
32
+ /** Number of candidate sequence numbers that required an uncached ODSP fetch. */
33
+ readonly sequenceNumberFetchCount: number;
30
34
  } | {
31
35
  /**
32
36
  * No sealed version has `sequenceNumber <= target` — the target predates retained history, or
@@ -35,6 +39,10 @@ export type BaseForSeq = {
35
39
  readonly kind: "noBaseVersion";
36
40
  /** The oldest sequence number that was resolved while searching, if any. */
37
41
  readonly oldestResolvedSeq?: number;
42
+ /** Number of sealed version candidates examined before exhausting retained history. */
43
+ readonly versionsProbed: number;
44
+ /** Number of candidate sequence numbers that required an uncached ODSP fetch. */
45
+ readonly sequenceNumberFetchCount: number;
38
46
  };
39
47
  /**
40
48
  * Selects the file version to use as the base for loading or replaying to a target sequence number.
@@ -1 +1 @@
1
- {"version":3,"file":"odspVersionManager.d.ts","sourceRoot":"","sources":["../../src/odspVersionManager/odspVersionManager.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAgBH,OAAO,EAEN,KAAK,2BAA2B,EAChC,KAAK,kBAAkB,EACvB,KAAK,uBAAuB,EAC5B,MAAM,6BAA6B,CAAC;AAKrC,YAAY,EAAE,kBAAkB,EAAE,uBAAuB,EAAE,MAAM,6BAA6B,CAAC;AAE/F;;GAEG;AACH,MAAM,WAAW,eAAgB,SAAQ,kBAAkB;IAC1D;;OAEG;IACH,QAAQ,CAAC,cAAc,EAAE,MAAM,CAAC;CAChC;AAED;;;;;;;;;GASG;AACH,MAAM,MAAM,UAAU,GACnB;IACA,uEAAuE;IACvE,QAAQ,CAAC,IAAI,EAAE,OAAO,CAAC;IACvB,QAAQ,CAAC,IAAI,EAAE,eAAe,CAAC;CAC9B,GACD;IACA;;;OAGG;IACH,QAAQ,CAAC,IAAI,EAAE,eAAe,CAAC;IAC/B,4EAA4E;IAC5E,QAAQ,CAAC,iBAAiB,CAAC,EAAE,MAAM,CAAC;CACnC,CAAC;AAEL;;GAEG;AACH,MAAM,WAAW,mBAAmB;IACnC;;;;;;;;;OASG;IACH,cAAc,CAAC,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,UAAU,CAAC,CAAC;CACpD;AAED;;;;;GAKG;AAGH,qBAAa,kBAAmB,YAAW,mBAAmB;IAQ1C,OAAO,CAAC,QAAQ,CAAC,OAAO;IAL3C,OAAO,CAAC,QAAQ,CAAC,QAAQ,CAAsC;IAG/D,OAAO,CAAC,QAAQ,CAAC,UAAU,CAAkD;gBAEzC,OAAO,EAAE,uBAAuB;IAEvD,cAAc,CAAC,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,UAAU,CAAC;YA2BlD,oBAAoB;IAqCrB,YAAY,IAAI,OAAO,CAAC,eAAe,EAAE,CAAC;YAiBzC,UAAU;YAQV,mBAAmB;CAOjC;AAED;;GAEG;AACH,wBAAgB,wBAAwB,CACvC,KAAK,EAAE,2BAA2B,GAChC,mBAAmB,CAErB"}
1
+ {"version":3,"file":"odspVersionManager.d.ts","sourceRoot":"","sources":["../../src/odspVersionManager/odspVersionManager.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAgBH,OAAO,EAEN,KAAK,2BAA2B,EAChC,KAAK,kBAAkB,EACvB,KAAK,uBAAuB,EAC5B,MAAM,6BAA6B,CAAC;AAKrC,YAAY,EAAE,kBAAkB,EAAE,uBAAuB,EAAE,MAAM,6BAA6B,CAAC;AAE/F;;GAEG;AACH,MAAM,WAAW,eAAgB,SAAQ,kBAAkB;IAC1D;;OAEG;IACH,QAAQ,CAAC,cAAc,EAAE,MAAM,CAAC;CAChC;AAED;;;;;;;;;GASG;AACH,MAAM,MAAM,UAAU,GACnB;IACA,uEAAuE;IACvE,QAAQ,CAAC,IAAI,EAAE,OAAO,CAAC;IACvB,QAAQ,CAAC,IAAI,EAAE,eAAe,CAAC;IAC/B,8EAA8E;IAC9E,QAAQ,CAAC,cAAc,EAAE,MAAM,CAAC;IAChC,iFAAiF;IACjF,QAAQ,CAAC,wBAAwB,EAAE,MAAM,CAAC;CACzC,GACD;IACA;;;OAGG;IACH,QAAQ,CAAC,IAAI,EAAE,eAAe,CAAC;IAC/B,4EAA4E;IAC5E,QAAQ,CAAC,iBAAiB,CAAC,EAAE,MAAM,CAAC;IACpC,uFAAuF;IACvF,QAAQ,CAAC,cAAc,EAAE,MAAM,CAAC;IAChC,iFAAiF;IACjF,QAAQ,CAAC,wBAAwB,EAAE,MAAM,CAAC;CACzC,CAAC;AAEL;;GAEG;AACH,MAAM,WAAW,mBAAmB;IACnC;;;;;;;;;OASG;IACH,cAAc,CAAC,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,UAAU,CAAC,CAAC;CACpD;AAED;;;;;GAKG;AAGH,qBAAa,kBAAmB,YAAW,mBAAmB;IAQ1C,OAAO,CAAC,QAAQ,CAAC,OAAO;IAL3C,OAAO,CAAC,QAAQ,CAAC,QAAQ,CAAsC;IAG/D,OAAO,CAAC,QAAQ,CAAC,UAAU,CAAkD;gBAEzC,OAAO,EAAE,uBAAuB;IAEvD,cAAc,CAAC,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,UAAU,CAAC;YAqClD,oBAAoB;IAsCrB,YAAY,IAAI,OAAO,CAAC,eAAe,EAAE,CAAC;YAiBzC,UAAU;YASV,mBAAmB;CAOjC;AAED;;GAEG;AACH,wBAAgB,wBAAwB,CACvC,KAAK,EAAE,2BAA2B,GAChC,mBAAmB,CAErB"}
@@ -46,8 +46,13 @@ class OdspVersionManager {
46
46
  // base — or noBaseVersion, reporting the oldest sequence number seen.
47
47
  const candidates = versions.slice(1);
48
48
  let oldestResolvedSeq;
49
+ let versionsProbed = 0;
50
+ let sequenceNumberFetchCount = 0;
49
51
  for (const version of candidates) {
50
- const sequenceNumber = await this.resolveSeq(version.versionId);
52
+ versionsProbed++;
53
+ const sequenceNumber = await this.resolveSeq(version.versionId, () => {
54
+ sequenceNumberFetchCount++;
55
+ });
51
56
  oldestResolvedSeq =
52
57
  oldestResolvedSeq === undefined
53
58
  ? sequenceNumber
@@ -56,10 +61,15 @@ class OdspVersionManager {
56
61
  const base = { ...version, sequenceNumber };
57
62
  // Confirm the chosen base shares the live document's lineage before handing it back
58
63
  await this.validateLineageEpoch(base);
59
- return { kind: "found", base };
64
+ return { kind: "found", base, versionsProbed, sequenceNumberFetchCount };
60
65
  }
61
66
  }
62
- return { kind: "noBaseVersion", oldestResolvedSeq };
67
+ return {
68
+ kind: "noBaseVersion",
69
+ oldestResolvedSeq,
70
+ versionsProbed,
71
+ sequenceNumberFetchCount,
72
+ };
63
73
  }
64
74
  async validateLineageEpoch(base) {
65
75
  // The live document's epoch can change (a restore or download-and-reupload bumps it), so it is
@@ -86,6 +96,7 @@ class OdspVersionManager {
86
96
  driverVersion: packageVersion_js_1.pkgVersion,
87
97
  serverEpoch: liveEpoch,
88
98
  clientEpoch: baseEpoch,
99
+ versionMarkAvailabilityOutcome: "lineageMismatch",
89
100
  });
90
101
  }
91
102
  }
@@ -102,10 +113,13 @@ class OdspVersionManager {
102
113
  : await this.resolveSeq(version.versionId),
103
114
  })));
104
115
  }
105
- async resolveSeq(versionId) {
116
+ async resolveSeq(versionId, onCacheMiss) {
106
117
  // Cached indefinitely (a sealed version's number is fixed); concurrent calls coalesce and a failed
107
118
  // resolution is evicted so a later call retries.
108
- return this.seqCache.addOrGet(versionId, async () => this.fetcher.resolveSequenceNumber(versionId));
119
+ return this.seqCache.addOrGet(versionId, async () => {
120
+ onCacheMiss?.();
121
+ return this.fetcher.resolveSequenceNumber(versionId);
122
+ });
109
123
  }
110
124
  async resolveVersionEpoch(versionId) {
111
125
  // Cached like resolveSeq (a sealed version's epoch is fixed). The live document's epoch is read
@@ -1 +1 @@
1
- {"version":3,"file":"odspVersionManager.js","sourceRoot":"","sources":["../../src/odspVersionManager/odspVersionManager.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/**\n * Selects the ODSP file version whose snapshot sits at or before a target Fluid sequence number —\n * the base to load or replay from when materializing a document at a point in time.\n *\n * The selection logic depends on an injected {@link IOdspFileVersionFetcher}, so it is independent of\n * how versions are enumerated and resolved (real ODSP, a test double, or an alternative backend).\n */\n\nimport { PromiseCache } from \"@fluidframework/core-utils/internal\";\nimport { NonRetryableError } from \"@fluidframework/driver-utils/internal\";\nimport { OdspErrorTypes } from \"@fluidframework/odsp-driver-definitions/internal\";\n\nimport { pkgVersion as driverVersion } from \"../packageVersion.js\";\n\nimport {\n\tcreateOdspFileVersionFetcher,\n\ttype OdspFileVersionFetcherProps,\n\ttype OdspFileVersionRef,\n\ttype IOdspFileVersionFetcher,\n} from \"./odspFileVersionFetcher.js\";\n\n// Re-exported so consumers (and this module's own index) can keep importing these fetcher-owned\n// types from the version manager. The definitions live in odspFileVersionFetcher.ts so that file\n// does not depend on this one, avoiding a circular dependency between the two modules.\nexport type { OdspFileVersionRef, IOdspFileVersionFetcher } from \"./odspFileVersionFetcher.js\";\n\n/**\n * An ODSP file version together with its resolved Fluid sequence number.\n */\nexport interface ResolvedVersion extends OdspFileVersionRef {\n\t/**\n\t * The Fluid sequence number the version's snapshot represents.\n\t */\n\treadonly sequenceNumber: number;\n}\n\n/**\n * Result of resolving the base version for a target sequence number.\n *\n * @remarks\n * The tip (newest) version is excluded from base selection, so when the target is at or after the head\n * the base is the newest *sealed* version with `seq <= target` (a normal `found`); if the file's only\n * version is the tip, the result is `noBaseVersion`. The wired consumer surfaces `noBaseVersion` as a\n * `UsageError`; loading the live file for a near-head target is a possible future consumer choice, not\n * current behavior.\n */\nexport type BaseForSeq =\n\t| {\n\t\t\t/** A recoverable version with `sequenceNumber <= target` was found. */\n\t\t\treadonly kind: \"found\";\n\t\t\treadonly base: ResolvedVersion;\n\t }\n\t| {\n\t\t\t/**\n\t\t\t * No sealed version has `sequenceNumber <= target` — the target predates retained history, or\n\t\t\t * the only version is the excluded tip.\n\t\t\t */\n\t\t\treadonly kind: \"noBaseVersion\";\n\t\t\t/** The oldest sequence number that was resolved while searching, if any. */\n\t\t\treadonly oldestResolvedSeq?: number;\n\t };\n\n/**\n * Selects the file version to use as the base for loading or replaying to a target sequence number.\n */\nexport interface IOdspVersionManager {\n\t/**\n\t * Given a target sequence number, return the closest version at or before it (`found`), or\n\t * `noBaseVersion` if the target predates the oldest retained version.\n\t *\n\t * @remarks\n\t * A `found` base is guaranteed to share the live document's ODSP epoch (lineage): before returning\n\t * it, the chosen base's epoch is compared with the live document's, and a mismatch throws a non-retryable error\n\t * rather than returning a base that cannot be replayed. Op availability is enforced separately and\n\t * lazily as the loader reads the bridging ops.\n\t */\n\tfindBaseForSeq(target: number): Promise<BaseForSeq>;\n}\n\n/**\n * Default {@link IOdspVersionManager}. Caches resolved sequence numbers (which never change); the version\n * list is re-enumerated on each query rather than cached, since new versions are cut over time. The\n * resolution strategy (eager, newest-to-oldest, stopping at the first usable base) is hidden behind\n * {@link findBaseForSeq} and can change without affecting callers.\n */\n// Exported only so the same-package tests can construct it with a fake IOdspFileVersionFetcher.\n// Deliberately kept out of the folder barrel and the package public index, so it is not public API.\nexport class OdspVersionManager implements IOdspVersionManager {\n\t// Sealed versions' sequence numbers, memoized so each is resolved at most once per manager instance\n\t// (a sealed version's number is fixed once the version exists).\n\tprivate readonly seqCache = new PromiseCache<string, number>();\n\t// Sealed versions' ODSP epochs, memoized like their sequence numbers. The live document's epoch is NOT\n\t// cached — it can change (restore/reupload), so validateLineageEpoch always reads it fresh.\n\tprivate readonly epochCache = new PromiseCache<string, string | undefined>();\n\n\tpublic constructor(private readonly fetcher: IOdspFileVersionFetcher) {}\n\n\tpublic async findBaseForSeq(target: number): Promise<BaseForSeq> {\n\t\t// Re-enumerate the list each call (it changes as new versions are cut).\n\t\tconst versions = await this.fetcher.listFileVersions();\n\n\t\t// Start past the tip (index 0): the newest version's sequence number can still advance until a newer\n\t\t// version is cut, so it is treated as the live head rather than a stable base. Scan the remaining\n\t\t// (sealed) versions newest-first and return the first with sequence number <= target — the closest\n\t\t// base — or noBaseVersion, reporting the oldest sequence number seen.\n\t\tconst candidates = versions.slice(1);\n\n\t\tlet oldestResolvedSeq: number | undefined;\n\t\tfor (const version of candidates) {\n\t\t\tconst sequenceNumber = await this.resolveSeq(version.versionId);\n\t\t\toldestResolvedSeq =\n\t\t\t\toldestResolvedSeq === undefined\n\t\t\t\t\t? sequenceNumber\n\t\t\t\t\t: Math.min(oldestResolvedSeq, sequenceNumber);\n\t\t\tif (sequenceNumber <= target) {\n\t\t\t\tconst base = { ...version, sequenceNumber };\n\t\t\t\t// Confirm the chosen base shares the live document's lineage before handing it back\n\t\t\t\tawait this.validateLineageEpoch(base);\n\t\t\t\treturn { kind: \"found\", base };\n\t\t\t}\n\t\t}\n\t\treturn { kind: \"noBaseVersion\", oldestResolvedSeq };\n\t}\n\n\tprivate async validateLineageEpoch(base: ResolvedVersion): Promise<void> {\n\t\t// The live document's epoch can change (a restore or download-and-reupload bumps it), so it is\n\t\t// always read fresh. A numbered version's snapshot is immutable, so its epoch never changes and\n\t\t// is cached per versionId (see resolveVersionEpoch).\n\t\tconst [liveEpoch, baseEpoch] = await Promise.all([\n\t\t\tthis.fetcher.getLiveDocumentEpoch(),\n\t\t\tthis.resolveVersionEpoch(base.versionId),\n\t\t]);\n\t\tif (liveEpoch === undefined || baseEpoch === undefined) {\n\t\t\tthrow new NonRetryableError(\n\t\t\t\t`Cannot verify that ODSP file version ${base.versionId} shares the live document's ` +\n\t\t\t\t\t`lineage: the storage response is missing an epoch (base epoch: ${baseEpoch ?? \"unknown\"}, ` +\n\t\t\t\t\t`live epoch: ${liveEpoch ?? \"unknown\"}).`,\n\t\t\t\tOdspErrorTypes.incorrectServerResponse,\n\t\t\t\t{\n\t\t\t\t\tdriverVersion,\n\t\t\t\t\tserverEpoch: liveEpoch,\n\t\t\t\t\tclientEpoch: baseEpoch,\n\t\t\t\t},\n\t\t\t);\n\t\t}\n\t\tif (liveEpoch !== baseEpoch) {\n\t\t\tthrow new NonRetryableError(\n\t\t\t\t`ODSP file version ${base.versionId} is on epoch \"${baseEpoch}\" but the live document is ` +\n\t\t\t\t\t`on epoch \"${liveEpoch}\". A binary file change (e.g. a version restore or ` +\n\t\t\t\t\t`download-and-reupload) renumbered the op stream, so ops cannot be replayed from this ` +\n\t\t\t\t\t`base onto the live document.`,\n\t\t\t\tOdspErrorTypes.fileOverwrittenInStorage,\n\t\t\t\t{\n\t\t\t\t\tdriverVersion,\n\t\t\t\t\tserverEpoch: liveEpoch,\n\t\t\t\t\tclientEpoch: baseEpoch,\n\t\t\t\t},\n\t\t\t);\n\t\t}\n\t}\n\n\tpublic async listVersions(): Promise<ResolvedVersion[]> {\n\t\tconst versions = await this.fetcher.listFileVersions();\n\t\t// Resolution order does not matter, so resolve concurrently; the newest-first array order is\n\t\t// preserved by Promise.all regardless of completion order.\n\t\treturn Promise.all(\n\t\t\tversions.map(async (version, index) => ({\n\t\t\t\t...version,\n\t\t\t\t// Resolve the tip (index 0) fresh each call, since its sequence number can still change;\n\t\t\t\t// sealed versions come from the cache.\n\t\t\t\tsequenceNumber:\n\t\t\t\t\tindex === 0\n\t\t\t\t\t\t? await this.fetcher.resolveSequenceNumber(version.versionId)\n\t\t\t\t\t\t: await this.resolveSeq(version.versionId),\n\t\t\t})),\n\t\t);\n\t}\n\n\tprivate async resolveSeq(versionId: string): Promise<number> {\n\t\t// Cached indefinitely (a sealed version's number is fixed); concurrent calls coalesce and a failed\n\t\t// resolution is evicted so a later call retries.\n\t\treturn this.seqCache.addOrGet(versionId, async () =>\n\t\t\tthis.fetcher.resolveSequenceNumber(versionId),\n\t\t);\n\t}\n\n\tprivate async resolveVersionEpoch(versionId: string): Promise<string | undefined> {\n\t\t// Cached like resolveSeq (a sealed version's epoch is fixed). The live document's epoch is read\n\t\t// fresh instead (see validateLineageEpoch).\n\t\treturn this.epochCache.addOrGet(versionId, async () =>\n\t\t\tthis.fetcher.getRecoverableVersionEpoch(versionId),\n\t\t);\n\t}\n}\n\n/**\n * Create an {@link IOdspVersionManager} for a specific ODSP file, wired to the real ODSP REST APIs.\n */\nexport function createOdspVersionManager(\n\tprops: OdspFileVersionFetcherProps,\n): IOdspVersionManager {\n\treturn new OdspVersionManager(createOdspFileVersionFetcher(props));\n}\n"]}
1
+ {"version":3,"file":"odspVersionManager.js","sourceRoot":"","sources":["../../src/odspVersionManager/odspVersionManager.ts"],"names":[],"mappings":";AAAA;;;GAGG;;;AA4NH,4DAIC;AA9ND;;;;;;GAMG;AAEH,kEAAmE;AACnE,oEAA0E;AAC1E,+EAAkF;AAElF,4DAAmE;AAEnE,2EAKqC;AAoErC;;;;;GAKG;AACH,gGAAgG;AAChG,oGAAoG;AACpG,MAAa,kBAAkB;IAQM;IAPpC,oGAAoG;IACpG,gEAAgE;IAC/C,QAAQ,GAAG,IAAI,uBAAY,EAAkB,CAAC;IAC/D,uGAAuG;IACvG,4FAA4F;IAC3E,UAAU,GAAG,IAAI,uBAAY,EAA8B,CAAC;IAE7E,YAAoC,OAAgC;QAAhC,YAAO,GAAP,OAAO,CAAyB;IAAG,CAAC;IAEjE,KAAK,CAAC,cAAc,CAAC,MAAc;QACzC,wEAAwE;QACxE,MAAM,QAAQ,GAAG,MAAM,IAAI,CAAC,OAAO,CAAC,gBAAgB,EAAE,CAAC;QAEvD,qGAAqG;QACrG,kGAAkG;QAClG,mGAAmG;QACnG,sEAAsE;QACtE,MAAM,UAAU,GAAG,QAAQ,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC;QAErC,IAAI,iBAAqC,CAAC;QAC1C,IAAI,cAAc,GAAG,CAAC,CAAC;QACvB,IAAI,wBAAwB,GAAG,CAAC,CAAC;QACjC,KAAK,MAAM,OAAO,IAAI,UAAU,EAAE,CAAC;YAClC,cAAc,EAAE,CAAC;YACjB,MAAM,cAAc,GAAG,MAAM,IAAI,CAAC,UAAU,CAAC,OAAO,CAAC,SAAS,EAAE,GAAG,EAAE;gBACpE,wBAAwB,EAAE,CAAC;YAC5B,CAAC,CAAC,CAAC;YACH,iBAAiB;gBAChB,iBAAiB,KAAK,SAAS;oBAC9B,CAAC,CAAC,cAAc;oBAChB,CAAC,CAAC,IAAI,CAAC,GAAG,CAAC,iBAAiB,EAAE,cAAc,CAAC,CAAC;YAChD,IAAI,cAAc,IAAI,MAAM,EAAE,CAAC;gBAC9B,MAAM,IAAI,GAAG,EAAE,GAAG,OAAO,EAAE,cAAc,EAAE,CAAC;gBAC5C,oFAAoF;gBACpF,MAAM,IAAI,CAAC,oBAAoB,CAAC,IAAI,CAAC,CAAC;gBACtC,OAAO,EAAE,IAAI,EAAE,OAAO,EAAE,IAAI,EAAE,cAAc,EAAE,wBAAwB,EAAE,CAAC;YAC1E,CAAC;QACF,CAAC;QACD,OAAO;YACN,IAAI,EAAE,eAAe;YACrB,iBAAiB;YACjB,cAAc;YACd,wBAAwB;SACxB,CAAC;IACH,CAAC;IAEO,KAAK,CAAC,oBAAoB,CAAC,IAAqB;QACvD,+FAA+F;QAC/F,gGAAgG;QAChG,qDAAqD;QACrD,MAAM,CAAC,SAAS,EAAE,SAAS,CAAC,GAAG,MAAM,OAAO,CAAC,GAAG,CAAC;YAChD,IAAI,CAAC,OAAO,CAAC,oBAAoB,EAAE;YACnC,IAAI,CAAC,mBAAmB,CAAC,IAAI,CAAC,SAAS,CAAC;SACxC,CAAC,CAAC;QACH,IAAI,SAAS,KAAK,SAAS,IAAI,SAAS,KAAK,SAAS,EAAE,CAAC;YACxD,MAAM,IAAI,4BAAiB,CAC1B,wCAAwC,IAAI,CAAC,SAAS,8BAA8B;gBACnF,kEAAkE,SAAS,IAAI,SAAS,IAAI;gBAC5F,eAAe,SAAS,IAAI,SAAS,IAAI,EAC1C,yBAAc,CAAC,uBAAuB,EACtC;gBACC,aAAa,EAAb,8BAAa;gBACb,WAAW,EAAE,SAAS;gBACtB,WAAW,EAAE,SAAS;aACtB,CACD,CAAC;QACH,CAAC;QACD,IAAI,SAAS,KAAK,SAAS,EAAE,CAAC;YAC7B,MAAM,IAAI,4BAAiB,CAC1B,qBAAqB,IAAI,CAAC,SAAS,iBAAiB,SAAS,6BAA6B;gBACzF,aAAa,SAAS,qDAAqD;gBAC3E,uFAAuF;gBACvF,8BAA8B,EAC/B,yBAAc,CAAC,wBAAwB,EACvC;gBACC,aAAa,EAAb,8BAAa;gBACb,WAAW,EAAE,SAAS;gBACtB,WAAW,EAAE,SAAS;gBACtB,8BAA8B,EAAE,iBAAiB;aACjD,CACD,CAAC;QACH,CAAC;IACF,CAAC;IAEM,KAAK,CAAC,YAAY;QACxB,MAAM,QAAQ,GAAG,MAAM,IAAI,CAAC,OAAO,CAAC,gBAAgB,EAAE,CAAC;QACvD,6FAA6F;QAC7F,2DAA2D;QAC3D,OAAO,OAAO,CAAC,GAAG,CACjB,QAAQ,CAAC,GAAG,CAAC,KAAK,EAAE,OAAO,EAAE,KAAK,EAAE,EAAE,CAAC,CAAC;YACvC,GAAG,OAAO;YACV,yFAAyF;YACzF,uCAAuC;YACvC,cAAc,EACb,KAAK,KAAK,CAAC;gBACV,CAAC,CAAC,MAAM,IAAI,CAAC,OAAO,CAAC,qBAAqB,CAAC,OAAO,CAAC,SAAS,CAAC;gBAC7D,CAAC,CAAC,MAAM,IAAI,CAAC,UAAU,CAAC,OAAO,CAAC,SAAS,CAAC;SAC5C,CAAC,CAAC,CACH,CAAC;IACH,CAAC;IAEO,KAAK,CAAC,UAAU,CAAC,SAAiB,EAAE,WAAwB;QACnE,mGAAmG;QACnG,iDAAiD;QACjD,OAAO,IAAI,CAAC,QAAQ,CAAC,QAAQ,CAAC,SAAS,EAAE,KAAK,IAAI,EAAE;YACnD,WAAW,EAAE,EAAE,CAAC;YAChB,OAAO,IAAI,CAAC,OAAO,CAAC,qBAAqB,CAAC,SAAS,CAAC,CAAC;QACtD,CAAC,CAAC,CAAC;IACJ,CAAC;IAEO,KAAK,CAAC,mBAAmB,CAAC,SAAiB;QAClD,gGAAgG;QAChG,4CAA4C;QAC5C,OAAO,IAAI,CAAC,UAAU,CAAC,QAAQ,CAAC,SAAS,EAAE,KAAK,IAAI,EAAE,CACrD,IAAI,CAAC,OAAO,CAAC,0BAA0B,CAAC,SAAS,CAAC,CAClD,CAAC;IACH,CAAC;CACD;AAtHD,gDAsHC;AAED;;GAEG;AACH,SAAgB,wBAAwB,CACvC,KAAkC;IAElC,OAAO,IAAI,kBAAkB,CAAC,IAAA,wDAA4B,EAAC,KAAK,CAAC,CAAC,CAAC;AACpE,CAAC","sourcesContent":["/*!\n * Copyright (c) Microsoft Corporation and contributors. All rights reserved.\n * Licensed under the MIT License.\n */\n\n/**\n * Selects the ODSP file version whose snapshot sits at or before a target Fluid sequence number —\n * the base to load or replay from when materializing a document at a point in time.\n *\n * The selection logic depends on an injected {@link IOdspFileVersionFetcher}, so it is independent of\n * how versions are enumerated and resolved (real ODSP, a test double, or an alternative backend).\n */\n\nimport { PromiseCache } from \"@fluidframework/core-utils/internal\";\nimport { NonRetryableError } from \"@fluidframework/driver-utils/internal\";\nimport { OdspErrorTypes } from \"@fluidframework/odsp-driver-definitions/internal\";\n\nimport { pkgVersion as driverVersion } from \"../packageVersion.js\";\n\nimport {\n\tcreateOdspFileVersionFetcher,\n\ttype OdspFileVersionFetcherProps,\n\ttype OdspFileVersionRef,\n\ttype IOdspFileVersionFetcher,\n} from \"./odspFileVersionFetcher.js\";\n\n// Re-exported so consumers (and this module's own index) can keep importing these fetcher-owned\n// types from the version manager. The definitions live in odspFileVersionFetcher.ts so that file\n// does not depend on this one, avoiding a circular dependency between the two modules.\nexport type { OdspFileVersionRef, IOdspFileVersionFetcher } from \"./odspFileVersionFetcher.js\";\n\n/**\n * An ODSP file version together with its resolved Fluid sequence number.\n */\nexport interface ResolvedVersion extends OdspFileVersionRef {\n\t/**\n\t * The Fluid sequence number the version's snapshot represents.\n\t */\n\treadonly sequenceNumber: number;\n}\n\n/**\n * Result of resolving the base version for a target sequence number.\n *\n * @remarks\n * The tip (newest) version is excluded from base selection, so when the target is at or after the head\n * the base is the newest *sealed* version with `seq <= target` (a normal `found`); if the file's only\n * version is the tip, the result is `noBaseVersion`. The wired consumer surfaces `noBaseVersion` as a\n * `UsageError`; loading the live file for a near-head target is a possible future consumer choice, not\n * current behavior.\n */\nexport type BaseForSeq =\n\t| {\n\t\t\t/** A recoverable version with `sequenceNumber <= target` was found. */\n\t\t\treadonly kind: \"found\";\n\t\t\treadonly base: ResolvedVersion;\n\t\t\t/** Number of sealed version candidates examined before selecting the base. */\n\t\t\treadonly versionsProbed: number;\n\t\t\t/** Number of candidate sequence numbers that required an uncached ODSP fetch. */\n\t\t\treadonly sequenceNumberFetchCount: number;\n\t }\n\t| {\n\t\t\t/**\n\t\t\t * No sealed version has `sequenceNumber <= target` — the target predates retained history, or\n\t\t\t * the only version is the excluded tip.\n\t\t\t */\n\t\t\treadonly kind: \"noBaseVersion\";\n\t\t\t/** The oldest sequence number that was resolved while searching, if any. */\n\t\t\treadonly oldestResolvedSeq?: number;\n\t\t\t/** Number of sealed version candidates examined before exhausting retained history. */\n\t\t\treadonly versionsProbed: number;\n\t\t\t/** Number of candidate sequence numbers that required an uncached ODSP fetch. */\n\t\t\treadonly sequenceNumberFetchCount: number;\n\t };\n\n/**\n * Selects the file version to use as the base for loading or replaying to a target sequence number.\n */\nexport interface IOdspVersionManager {\n\t/**\n\t * Given a target sequence number, return the closest version at or before it (`found`), or\n\t * `noBaseVersion` if the target predates the oldest retained version.\n\t *\n\t * @remarks\n\t * A `found` base is guaranteed to share the live document's ODSP epoch (lineage): before returning\n\t * it, the chosen base's epoch is compared with the live document's, and a mismatch throws a non-retryable error\n\t * rather than returning a base that cannot be replayed. Op availability is enforced separately and\n\t * lazily as the loader reads the bridging ops.\n\t */\n\tfindBaseForSeq(target: number): Promise<BaseForSeq>;\n}\n\n/**\n * Default {@link IOdspVersionManager}. Caches resolved sequence numbers (which never change); the version\n * list is re-enumerated on each query rather than cached, since new versions are cut over time. The\n * resolution strategy (eager, newest-to-oldest, stopping at the first usable base) is hidden behind\n * {@link findBaseForSeq} and can change without affecting callers.\n */\n// Exported only so the same-package tests can construct it with a fake IOdspFileVersionFetcher.\n// Deliberately kept out of the folder barrel and the package public index, so it is not public API.\nexport class OdspVersionManager implements IOdspVersionManager {\n\t// Sealed versions' sequence numbers, memoized so each is resolved at most once per manager instance\n\t// (a sealed version's number is fixed once the version exists).\n\tprivate readonly seqCache = new PromiseCache<string, number>();\n\t// Sealed versions' ODSP epochs, memoized like their sequence numbers. The live document's epoch is NOT\n\t// cached — it can change (restore/reupload), so validateLineageEpoch always reads it fresh.\n\tprivate readonly epochCache = new PromiseCache<string, string | undefined>();\n\n\tpublic constructor(private readonly fetcher: IOdspFileVersionFetcher) {}\n\n\tpublic async findBaseForSeq(target: number): Promise<BaseForSeq> {\n\t\t// Re-enumerate the list each call (it changes as new versions are cut).\n\t\tconst versions = await this.fetcher.listFileVersions();\n\n\t\t// Start past the tip (index 0): the newest version's sequence number can still advance until a newer\n\t\t// version is cut, so it is treated as the live head rather than a stable base. Scan the remaining\n\t\t// (sealed) versions newest-first and return the first with sequence number <= target — the closest\n\t\t// base — or noBaseVersion, reporting the oldest sequence number seen.\n\t\tconst candidates = versions.slice(1);\n\n\t\tlet oldestResolvedSeq: number | undefined;\n\t\tlet versionsProbed = 0;\n\t\tlet sequenceNumberFetchCount = 0;\n\t\tfor (const version of candidates) {\n\t\t\tversionsProbed++;\n\t\t\tconst sequenceNumber = await this.resolveSeq(version.versionId, () => {\n\t\t\t\tsequenceNumberFetchCount++;\n\t\t\t});\n\t\t\toldestResolvedSeq =\n\t\t\t\toldestResolvedSeq === undefined\n\t\t\t\t\t? sequenceNumber\n\t\t\t\t\t: Math.min(oldestResolvedSeq, sequenceNumber);\n\t\t\tif (sequenceNumber <= target) {\n\t\t\t\tconst base = { ...version, sequenceNumber };\n\t\t\t\t// Confirm the chosen base shares the live document's lineage before handing it back\n\t\t\t\tawait this.validateLineageEpoch(base);\n\t\t\t\treturn { kind: \"found\", base, versionsProbed, sequenceNumberFetchCount };\n\t\t\t}\n\t\t}\n\t\treturn {\n\t\t\tkind: \"noBaseVersion\",\n\t\t\toldestResolvedSeq,\n\t\t\tversionsProbed,\n\t\t\tsequenceNumberFetchCount,\n\t\t};\n\t}\n\n\tprivate async validateLineageEpoch(base: ResolvedVersion): Promise<void> {\n\t\t// The live document's epoch can change (a restore or download-and-reupload bumps it), so it is\n\t\t// always read fresh. A numbered version's snapshot is immutable, so its epoch never changes and\n\t\t// is cached per versionId (see resolveVersionEpoch).\n\t\tconst [liveEpoch, baseEpoch] = await Promise.all([\n\t\t\tthis.fetcher.getLiveDocumentEpoch(),\n\t\t\tthis.resolveVersionEpoch(base.versionId),\n\t\t]);\n\t\tif (liveEpoch === undefined || baseEpoch === undefined) {\n\t\t\tthrow new NonRetryableError(\n\t\t\t\t`Cannot verify that ODSP file version ${base.versionId} shares the live document's ` +\n\t\t\t\t\t`lineage: the storage response is missing an epoch (base epoch: ${baseEpoch ?? \"unknown\"}, ` +\n\t\t\t\t\t`live epoch: ${liveEpoch ?? \"unknown\"}).`,\n\t\t\t\tOdspErrorTypes.incorrectServerResponse,\n\t\t\t\t{\n\t\t\t\t\tdriverVersion,\n\t\t\t\t\tserverEpoch: liveEpoch,\n\t\t\t\t\tclientEpoch: baseEpoch,\n\t\t\t\t},\n\t\t\t);\n\t\t}\n\t\tif (liveEpoch !== baseEpoch) {\n\t\t\tthrow new NonRetryableError(\n\t\t\t\t`ODSP file version ${base.versionId} is on epoch \"${baseEpoch}\" but the live document is ` +\n\t\t\t\t\t`on epoch \"${liveEpoch}\". A binary file change (e.g. a version restore or ` +\n\t\t\t\t\t`download-and-reupload) renumbered the op stream, so ops cannot be replayed from this ` +\n\t\t\t\t\t`base onto the live document.`,\n\t\t\t\tOdspErrorTypes.fileOverwrittenInStorage,\n\t\t\t\t{\n\t\t\t\t\tdriverVersion,\n\t\t\t\t\tserverEpoch: liveEpoch,\n\t\t\t\t\tclientEpoch: baseEpoch,\n\t\t\t\t\tversionMarkAvailabilityOutcome: \"lineageMismatch\",\n\t\t\t\t},\n\t\t\t);\n\t\t}\n\t}\n\n\tpublic async listVersions(): Promise<ResolvedVersion[]> {\n\t\tconst versions = await this.fetcher.listFileVersions();\n\t\t// Resolution order does not matter, so resolve concurrently; the newest-first array order is\n\t\t// preserved by Promise.all regardless of completion order.\n\t\treturn Promise.all(\n\t\t\tversions.map(async (version, index) => ({\n\t\t\t\t...version,\n\t\t\t\t// Resolve the tip (index 0) fresh each call, since its sequence number can still change;\n\t\t\t\t// sealed versions come from the cache.\n\t\t\t\tsequenceNumber:\n\t\t\t\t\tindex === 0\n\t\t\t\t\t\t? await this.fetcher.resolveSequenceNumber(version.versionId)\n\t\t\t\t\t\t: await this.resolveSeq(version.versionId),\n\t\t\t})),\n\t\t);\n\t}\n\n\tprivate async resolveSeq(versionId: string, onCacheMiss?: () => void): Promise<number> {\n\t\t// Cached indefinitely (a sealed version's number is fixed); concurrent calls coalesce and a failed\n\t\t// resolution is evicted so a later call retries.\n\t\treturn this.seqCache.addOrGet(versionId, async () => {\n\t\t\tonCacheMiss?.();\n\t\t\treturn this.fetcher.resolveSequenceNumber(versionId);\n\t\t});\n\t}\n\n\tprivate async resolveVersionEpoch(versionId: string): Promise<string | undefined> {\n\t\t// Cached like resolveSeq (a sealed version's epoch is fixed). The live document's epoch is read\n\t\t// fresh instead (see validateLineageEpoch).\n\t\treturn this.epochCache.addOrGet(versionId, async () =>\n\t\t\tthis.fetcher.getRecoverableVersionEpoch(versionId),\n\t\t);\n\t}\n}\n\n/**\n * Create an {@link IOdspVersionManager} for a specific ODSP file, wired to the real ODSP REST APIs.\n */\nexport function createOdspVersionManager(\n\tprops: OdspFileVersionFetcherProps,\n): IOdspVersionManager {\n\treturn new OdspVersionManager(createOdspFileVersionFetcher(props));\n}\n"]}
@@ -5,5 +5,5 @@
5
5
  * THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY
6
6
  */
7
7
  export declare const pkgName = "@fluidframework/odsp-driver";
8
- export declare const pkgVersion = "3.2.0";
8
+ export declare const pkgVersion = "3.3.0";
9
9
  //# sourceMappingURL=packageVersion.d.ts.map
@@ -8,5 +8,5 @@
8
8
  Object.defineProperty(exports, "__esModule", { value: true });
9
9
  exports.pkgVersion = exports.pkgName = void 0;
10
10
  exports.pkgName = "@fluidframework/odsp-driver";
11
- exports.pkgVersion = "3.2.0";
11
+ exports.pkgVersion = "3.3.0";
12
12
  //# sourceMappingURL=packageVersion.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"packageVersion.js","sourceRoot":"","sources":["../src/packageVersion.ts"],"names":[],"mappings":";AAAA;;;;;GAKG;;;AAEU,QAAA,OAAO,GAAG,6BAA6B,CAAC;AACxC,QAAA,UAAU,GAAG,OAAO,CAAC","sourcesContent":["/*!\n * Copyright (c) Microsoft Corporation and contributors. All rights reserved.\n * Licensed under the MIT License.\n *\n * THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY\n */\n\nexport const pkgName = \"@fluidframework/odsp-driver\";\nexport const pkgVersion = \"3.2.0\";\n"]}
1
+ {"version":3,"file":"packageVersion.js","sourceRoot":"","sources":["../src/packageVersion.ts"],"names":[],"mappings":";AAAA;;;;;GAKG;;;AAEU,QAAA,OAAO,GAAG,6BAA6B,CAAC;AACxC,QAAA,UAAU,GAAG,OAAO,CAAC","sourcesContent":["/*!\n * Copyright (c) Microsoft Corporation and contributors. All rights reserved.\n * Licensed under the MIT License.\n *\n * THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY\n */\n\nexport const pkgName = \"@fluidframework/odsp-driver\";\nexport const pkgVersion = \"3.3.0\";\n"]}
@@ -1 +1 @@
1
- {"version":3,"file":"createPointInTimeDocumentService.d.ts","sourceRoot":"","sources":["../../src/pointInTimeDriver/createPointInTimeDocumentService.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,OAAO,KAAK,EACX,gBAAgB,EAChB,YAAY,EACZ,MAAM,6CAA6C,CAAC;AAErD,OAAO,KAAK,EACX,gBAAgB,EAEhB,MAAM,kDAAkD,CAAC;AAC1D,OAAO,EAEN,KAAK,kBAAkB,EACvB,MAAM,0CAA0C,CAAC;AAElD,OAAO,EAA6B,KAAK,YAAY,EAAE,MAAM,oBAAoB,CAAC;AAGlF,OAAO,KAAK,EAAE,kDAAkD,EAAE,MAAM,sCAAsC,CAAC;AAM/G,OAAO,EAEN,KAAK,mBAAmB,EAExB,MAAM,6CAA6C,CAAC;AAErD,OAAO,EAAE,8BAA8B,EAAE,MAAM,qCAAqC,CAAC;AAErF,UAAU,uCAAuC;IAChD,QAAQ,CAAC,oBAAoB,CAAC,EAAE,CAC/B,eAAe,EAAE,gBAAgB,EACjC,MAAM,EAAE,kBAAkB,EAC1B,YAAY,EAAE,YAAY,KACtB,mBAAmB,GAAG,OAAO,CAAC,mBAAmB,CAAC,CAAC;IACxD,QAAQ,CAAC,kBAAkB,CAAC,EAAE,CAC7B,WAAW,EAAE,YAAY,EACzB,WAAW,EAAE,MAAM,KACf,YAAY,GAAG,OAAO,CAAC,YAAY,CAAC,CAAC;CAC1C;AAiDD;;;;;;;;;;GAUG;AACH,wBAAsB,gCAAgC,CACrD,KAAK,EAAE,kDAAkD,GACvD,OAAO,CAAC,gBAAgB,CAAC,CAE3B;AAED;;;GAGG;AACH,wBAAsB,oCAAoC,CACzD,EACC,WAAW,EACX,oBAAoB,EACpB,MAAM,EACN,kBAAkB,EAClB,cAAc,EACd,eAAe,EACf,cAAc,EACd,qBAAqB,GACrB,EAAE,kDAAkD,EACrD,YAAY,GAAE,uCAA4C,GACxD,OAAO,CAAC,8BAA8B,CAAC,CAoEzC"}
1
+ {"version":3,"file":"createPointInTimeDocumentService.d.ts","sourceRoot":"","sources":["../../src/pointInTimeDriver/createPointInTimeDocumentService.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAGH,OAAO,KAAK,EACX,gBAAgB,EAChB,YAAY,EACZ,MAAM,6CAA6C,CAAC;AAErD,OAAO,KAAK,EACX,gBAAgB,EAEhB,MAAM,kDAAkD,CAAC;AAC1D,OAAO,EAGN,KAAK,kBAAkB,EACvB,MAAM,0CAA0C,CAAC;AAElD,OAAO,EAA6B,KAAK,YAAY,EAAE,MAAM,oBAAoB,CAAC;AAGlF,OAAO,KAAK,EAAE,kDAAkD,EAAE,MAAM,sCAAsC,CAAC;AAM/G,OAAO,EAEN,KAAK,mBAAmB,EAExB,MAAM,6CAA6C,CAAC;AAErD,OAAO,EAAE,8BAA8B,EAAE,MAAM,qCAAqC,CAAC;AAErF,UAAU,uCAAuC;IAChD,QAAQ,CAAC,oBAAoB,CAAC,EAAE,CAC/B,eAAe,EAAE,gBAAgB,EACjC,MAAM,EAAE,kBAAkB,EAC1B,YAAY,EAAE,YAAY,KACtB,mBAAmB,GAAG,OAAO,CAAC,mBAAmB,CAAC,CAAC;IACxD,QAAQ,CAAC,kBAAkB,CAAC,EAAE,CAC7B,WAAW,EAAE,YAAY,EACzB,WAAW,EAAE,MAAM,KACf,YAAY,GAAG,OAAO,CAAC,YAAY,CAAC,CAAC;CAC1C;AAiDD;;;;;;;;;;GAUG;AACH,wBAAsB,gCAAgC,CACrD,KAAK,EAAE,kDAAkD,GACvD,OAAO,CAAC,gBAAgB,CAAC,CAE3B;AAED;;;GAGG;AACH,wBAAsB,oCAAoC,CACzD,EACC,WAAW,EACX,oBAAoB,EACpB,MAAM,EACN,kBAAkB,EAClB,cAAc,EACd,eAAe,EACf,cAAc,EACd,qBAAqB,GACrB,EAAE,kDAAkD,EACrD,YAAY,GAAE,uCAA4C,GACxD,OAAO,CAAC,8BAA8B,CAAC,CAqHzC"}
@@ -6,6 +6,7 @@
6
6
  Object.defineProperty(exports, "__esModule", { value: true });
7
7
  exports.createPointInTimeDocumentService = createPointInTimeDocumentService;
8
8
  exports.createPointInTimeDocumentServiceCore = createPointInTimeDocumentServiceCore;
9
+ const client_utils_1 = require("@fluid-internal/client-utils");
9
10
  const internal_1 = require("@fluidframework/driver-utils/internal");
10
11
  const internal_2 = require("@fluidframework/telemetry-utils/internal");
11
12
  const epochTracker_js_1 = require("../epochTracker.js");
@@ -73,15 +74,54 @@ async function createPointInTimeDocumentServiceCore({ resolvedUrl, targetSequenc
73
74
  docId: odspResolvedUrl.hashedDocumentId,
74
75
  fileVersion: odspResolvedUrl.fileVersion,
75
76
  }, extLogger, clientIsSummarizer, requestHeaders);
76
- const versionManager = await (dependencies.createVersionManager ??
77
- (async (url, versionLogger, epochTracker) => createVersionManager(url, versionLogger, epochTracker, getStorageToken, requestHeaders)))(odspResolvedUrl, extLogger, cacheAndTracker.epochTracker);
78
- const baseResult = await versionManager.findBaseForSeq(targetSequenceNumber);
77
+ const baseSelectionStartTime = (0, client_utils_1.performanceNow)();
78
+ let baseResult;
79
+ try {
80
+ const versionManager = await (dependencies.createVersionManager ??
81
+ (async (url, versionLogger, epochTracker) => createVersionManager(url, versionLogger, epochTracker, getStorageToken, requestHeaders)))(odspResolvedUrl, extLogger, cacheAndTracker.epochTracker);
82
+ baseResult = await versionManager.findBaseForSeq(targetSequenceNumber);
83
+ }
84
+ catch (error) {
85
+ extLogger.sendErrorEvent({
86
+ eventName: "VersionMarkBaseVersionSelection",
87
+ outcome: "failed",
88
+ duration: (0, client_utils_1.performanceNow)() - baseSelectionStartTime,
89
+ targetSequenceNumber,
90
+ errorType: error?.errorType,
91
+ availabilityOutcome: (0, internal_2.isILoggingError)(error)
92
+ ? error.getTelemetryProperties().versionMarkAvailabilityOutcome
93
+ : undefined,
94
+ }, error);
95
+ throw error;
96
+ }
79
97
  if (baseResult.kind === "noBaseVersion") {
80
98
  const oldestResolvedSequenceDetail = baseResult.oldestResolvedSeq === undefined
81
99
  ? ""
82
100
  : ` The oldest resolved file version is at sequence number ${baseResult.oldestResolvedSeq}.`;
83
- throw new internal_1.UsageError(`No ODSP file version is available at or before sequence number ${targetSequenceNumber}.${oldestResolvedSequenceDetail}`);
101
+ const error = new internal_1.UsageError(`No ODSP file version is available at or before sequence number ${targetSequenceNumber}.${oldestResolvedSequenceDetail}`);
102
+ error.addTelemetryProperties({
103
+ versionMarkAvailabilityOutcome: "baseVersionMissing",
104
+ });
105
+ extLogger.sendErrorEvent({
106
+ eventName: "VersionMarkBaseVersionSelection",
107
+ outcome: "failed",
108
+ duration: (0, client_utils_1.performanceNow)() - baseSelectionStartTime,
109
+ targetSequenceNumber,
110
+ availabilityOutcome: "baseVersionMissing",
111
+ oldestResolvedSequenceNumber: baseResult.oldestResolvedSeq,
112
+ versionsProbed: baseResult.versionsProbed,
113
+ sequenceNumberFetchCount: baseResult.sequenceNumberFetchCount,
114
+ errorType: error.errorType,
115
+ });
116
+ throw error;
84
117
  }
118
+ extLogger.sendPerformanceEvent({
119
+ eventName: "VersionMarkBaseVersionSelection",
120
+ outcome: "succeeded",
121
+ duration: (0, client_utils_1.performanceNow)() - baseSelectionStartTime,
122
+ versionsProbed: baseResult.versionsProbed,
123
+ sequenceNumberFetchCount: baseResult.sequenceNumberFetchCount,
124
+ });
85
125
  const recoverableResolvedUrl = await (dependencies.resolveFileVersion ?? resolveFileVersion)(resolvedUrl, baseResult.base.versionId);
86
126
  const recoverableDocumentService = await createDocumentService(recoverableResolvedUrl, odspLogger, cacheAndTracker, clientIsSummarizer);
87
127
  let liveDocumentService;
@@ -93,7 +133,9 @@ async function createPointInTimeDocumentServiceCore({ resolvedUrl, targetSequenc
93
133
  recoverableDocumentService.dispose();
94
134
  }
95
135
  catch (disposeError) {
96
- extLogger.sendErrorEvent({ eventName: "PointInTimeRecoverableDocumentServiceDisposeError" }, disposeError);
136
+ extLogger.sendErrorEvent({
137
+ eventName: "VersionMarkRecoverableDocumentServiceDisposeError",
138
+ }, disposeError);
97
139
  }
98
140
  throw error;
99
141
  }
@@ -1 +1 @@
1
- {"version":3,"file":"createPointInTimeDocumentService.js","sourceRoot":"","sources":["../../src/pointInTimeDriver/createPointInTimeDocumentService.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\nimport type {\n\tIDocumentService,\n\tIResolvedUrl,\n} from \"@fluidframework/driver-definitions/internal\";\nimport { UsageError } from \"@fluidframework/driver-utils/internal\";\nimport type {\n\tIOdspResolvedUrl,\n\tIOdspUrlParts,\n} from \"@fluidframework/odsp-driver-definitions/internal\";\nimport {\n\tcreateChildLogger,\n\ttype TelemetryLoggerExt,\n} from \"@fluidframework/telemetry-utils/internal\";\n\nimport { createOdspCacheAndTracker, type EpochTracker } from \"../epochTracker.js\";\nimport { NonPersistentCache } from \"../odspCache.js\";\nimport { OdspDriverUrlResolver } from \"../odspDriverUrlResolver.js\";\nimport type { IOdspPointInTimeDocumentServiceImplementationProps } from \"../odspDocumentServiceFactoryCore.js\";\nimport {\n\tcreateOdspLogger,\n\tgetOdspResolvedUrl,\n\ttoInstrumentedOdspStorageTokenFetcher,\n} from \"../odspUtils.js\";\nimport {\n\tcreateOdspVersionManager,\n\ttype IOdspVersionManager,\n\t// eslint-disable-next-line import-x/no-internal-modules -- the feature implementation owns version selection\n} from \"../odspVersionManager/odspVersionManager.js\";\n\nimport { OdspPointInTimeDocumentService } from \"./odspPointInTimeDocumentService.js\";\n\ninterface IPointInTimeDocumentServiceDependencies {\n\treadonly createVersionManager?: (\n\t\todspResolvedUrl: IOdspResolvedUrl,\n\t\tlogger: TelemetryLoggerExt,\n\t\tepochTracker: EpochTracker,\n\t) => IOdspVersionManager | Promise<IOdspVersionManager>;\n\treadonly resolveFileVersion?: (\n\t\tresolvedUrl: IResolvedUrl,\n\t\tfileVersion: string,\n\t) => IResolvedUrl | Promise<IResolvedUrl>;\n}\n\nasync function createVersionManager(\n\todspResolvedUrl: IOdspResolvedUrl,\n\tlogger: TelemetryLoggerExt,\n\tepochTracker: EpochTracker,\n\tgetStorageToken: IOdspPointInTimeDocumentServiceImplementationProps[\"getStorageToken\"],\n\trequestHeaders?: Readonly<Record<string, string>>,\n): Promise<IOdspVersionManager> {\n\tconst urlParts: IOdspUrlParts = {\n\t\tsiteUrl: odspResolvedUrl.siteUrl,\n\t\tdriveId: odspResolvedUrl.driveId,\n\t\titemId: odspResolvedUrl.itemId,\n\t};\n\tconst getAuthHeader = toInstrumentedOdspStorageTokenFetcher(\n\t\tlogger,\n\t\turlParts,\n\t\tgetStorageToken,\n\t);\n\treturn createOdspVersionManager({\n\t\turlParts,\n\t\tgetAuthHeader,\n\t\tepochTracker,\n\t\tlogger,\n\t\trequestHeaders,\n\t});\n}\n\nasync function resolveFileVersion(\n\tresolvedUrl: IResolvedUrl,\n\tfileVersion: string,\n): Promise<IResolvedUrl> {\n\tconst odspResolvedUrl = getOdspResolvedUrl(resolvedUrl);\n\tconst query = new URLSearchParams({\n\t\tdriveId: odspResolvedUrl.driveId,\n\t\titemId: odspResolvedUrl.itemId,\n\t\tfileVersion,\n\t});\n\tif (odspResolvedUrl.dataStorePath !== undefined) {\n\t\tquery.set(\"path\", odspResolvedUrl.dataStorePath);\n\t}\n\tif (odspResolvedUrl.codeHint?.containerPackageName !== undefined) {\n\t\tquery.set(\"containerPackageName\", odspResolvedUrl.codeHint.containerPackageName);\n\t}\n\treturn new OdspDriverUrlResolver().resolve({\n\t\turl: `${odspResolvedUrl.siteUrl}?${query.toString()}`,\n\t});\n}\n\n/**\n * ODSP's point-in-time document service implementation.\n *\n * @remarks Import this function from the dedicated point-in-time entrypoint and inject it through\n * {@link IOdspDocumentServiceFactoryOptions.pointInTimeDocumentServiceImplementation}.\n *\n * @param props - ODSP service dependencies and point-in-time load parameters supplied by the factory.\n * @returns A read-only ODSP document service materialized at the requested sequence number.\n *\n * @legacy @beta\n */\nexport async function createPointInTimeDocumentService(\n\tprops: IOdspPointInTimeDocumentServiceImplementationProps,\n): Promise<IDocumentService> {\n\treturn createPointInTimeDocumentServiceCore(props);\n}\n\n/**\n * Creates the point-in-time service with replaceable dependencies for testing.\n * @internal\n */\nexport async function createPointInTimeDocumentServiceCore(\n\t{\n\t\tresolvedUrl,\n\t\ttargetSequenceNumber,\n\t\tlogger,\n\t\tclientIsSummarizer,\n\t\tpersistedCache,\n\t\tgetStorageToken,\n\t\trequestHeaders,\n\t\tcreateDocumentService,\n\t}: IOdspPointInTimeDocumentServiceImplementationProps,\n\tdependencies: IPointInTimeDocumentServiceDependencies = {},\n): Promise<OdspPointInTimeDocumentService> {\n\tconst odspLogger = createOdspLogger(logger);\n\tconst extLogger = createChildLogger({ logger: odspLogger });\n\tconst odspResolvedUrl = getOdspResolvedUrl(resolvedUrl);\n\tconst cacheAndTracker = createOdspCacheAndTracker(\n\t\tpersistedCache,\n\t\tnew NonPersistentCache(),\n\t\t{\n\t\t\tresolvedUrl: odspResolvedUrl,\n\t\t\tdocId: odspResolvedUrl.hashedDocumentId,\n\t\t\tfileVersion: odspResolvedUrl.fileVersion,\n\t\t},\n\t\textLogger,\n\t\tclientIsSummarizer,\n\t\trequestHeaders,\n\t);\n\n\tconst versionManager = await (\n\t\tdependencies.createVersionManager ??\n\t\t(async (url, versionLogger, epochTracker) =>\n\t\t\tcreateVersionManager(url, versionLogger, epochTracker, getStorageToken, requestHeaders))\n\t)(odspResolvedUrl, extLogger, cacheAndTracker.epochTracker);\n\tconst baseResult = await versionManager.findBaseForSeq(targetSequenceNumber);\n\tif (baseResult.kind === \"noBaseVersion\") {\n\t\tconst oldestResolvedSequenceDetail =\n\t\t\tbaseResult.oldestResolvedSeq === undefined\n\t\t\t\t? \"\"\n\t\t\t\t: ` The oldest resolved file version is at sequence number ${baseResult.oldestResolvedSeq}.`;\n\t\tthrow new UsageError(\n\t\t\t`No ODSP file version is available at or before sequence number ${targetSequenceNumber}.${oldestResolvedSequenceDetail}`,\n\t\t);\n\t}\n\n\tconst recoverableResolvedUrl = await (dependencies.resolveFileVersion ?? resolveFileVersion)(\n\t\tresolvedUrl,\n\t\tbaseResult.base.versionId,\n\t);\n\tconst recoverableDocumentService = await createDocumentService(\n\t\trecoverableResolvedUrl,\n\t\todspLogger,\n\t\tcacheAndTracker,\n\t\tclientIsSummarizer,\n\t);\n\tlet liveDocumentService: IDocumentService;\n\ttry {\n\t\tliveDocumentService = await createDocumentService(\n\t\t\tresolvedUrl,\n\t\t\todspLogger,\n\t\t\tcacheAndTracker,\n\t\t\tclientIsSummarizer,\n\t\t);\n\t} catch (error) {\n\t\ttry {\n\t\t\trecoverableDocumentService.dispose();\n\t\t} catch (disposeError) {\n\t\t\textLogger.sendErrorEvent(\n\t\t\t\t{ eventName: \"PointInTimeRecoverableDocumentServiceDisposeError\" },\n\t\t\t\tdisposeError,\n\t\t\t);\n\t\t}\n\t\tthrow error;\n\t}\n\treturn new OdspPointInTimeDocumentService(\n\t\trecoverableResolvedUrl,\n\t\trecoverableDocumentService,\n\t\tliveDocumentService,\n\t\ttargetSequenceNumber,\n\t);\n}\n"]}
1
+ {"version":3,"file":"createPointInTimeDocumentService.js","sourceRoot":"","sources":["../../src/pointInTimeDriver/createPointInTimeDocumentService.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\nimport { performanceNow } from \"@fluid-internal/client-utils\";\nimport type {\n\tIDocumentService,\n\tIResolvedUrl,\n} from \"@fluidframework/driver-definitions/internal\";\nimport { UsageError } from \"@fluidframework/driver-utils/internal\";\nimport type {\n\tIOdspResolvedUrl,\n\tIOdspUrlParts,\n} from \"@fluidframework/odsp-driver-definitions/internal\";\nimport {\n\tcreateChildLogger,\n\tisILoggingError,\n\ttype TelemetryLoggerExt,\n} from \"@fluidframework/telemetry-utils/internal\";\n\nimport { createOdspCacheAndTracker, type EpochTracker } from \"../epochTracker.js\";\nimport { NonPersistentCache } from \"../odspCache.js\";\nimport { OdspDriverUrlResolver } from \"../odspDriverUrlResolver.js\";\nimport type { IOdspPointInTimeDocumentServiceImplementationProps } from \"../odspDocumentServiceFactoryCore.js\";\nimport {\n\tcreateOdspLogger,\n\tgetOdspResolvedUrl,\n\ttoInstrumentedOdspStorageTokenFetcher,\n} from \"../odspUtils.js\";\nimport {\n\tcreateOdspVersionManager,\n\ttype IOdspVersionManager,\n\t// eslint-disable-next-line import-x/no-internal-modules -- the feature implementation owns version selection\n} from \"../odspVersionManager/odspVersionManager.js\";\n\nimport { OdspPointInTimeDocumentService } from \"./odspPointInTimeDocumentService.js\";\n\ninterface IPointInTimeDocumentServiceDependencies {\n\treadonly createVersionManager?: (\n\t\todspResolvedUrl: IOdspResolvedUrl,\n\t\tlogger: TelemetryLoggerExt,\n\t\tepochTracker: EpochTracker,\n\t) => IOdspVersionManager | Promise<IOdspVersionManager>;\n\treadonly resolveFileVersion?: (\n\t\tresolvedUrl: IResolvedUrl,\n\t\tfileVersion: string,\n\t) => IResolvedUrl | Promise<IResolvedUrl>;\n}\n\nasync function createVersionManager(\n\todspResolvedUrl: IOdspResolvedUrl,\n\tlogger: TelemetryLoggerExt,\n\tepochTracker: EpochTracker,\n\tgetStorageToken: IOdspPointInTimeDocumentServiceImplementationProps[\"getStorageToken\"],\n\trequestHeaders?: Readonly<Record<string, string>>,\n): Promise<IOdspVersionManager> {\n\tconst urlParts: IOdspUrlParts = {\n\t\tsiteUrl: odspResolvedUrl.siteUrl,\n\t\tdriveId: odspResolvedUrl.driveId,\n\t\titemId: odspResolvedUrl.itemId,\n\t};\n\tconst getAuthHeader = toInstrumentedOdspStorageTokenFetcher(\n\t\tlogger,\n\t\turlParts,\n\t\tgetStorageToken,\n\t);\n\treturn createOdspVersionManager({\n\t\turlParts,\n\t\tgetAuthHeader,\n\t\tepochTracker,\n\t\tlogger,\n\t\trequestHeaders,\n\t});\n}\n\nasync function resolveFileVersion(\n\tresolvedUrl: IResolvedUrl,\n\tfileVersion: string,\n): Promise<IResolvedUrl> {\n\tconst odspResolvedUrl = getOdspResolvedUrl(resolvedUrl);\n\tconst query = new URLSearchParams({\n\t\tdriveId: odspResolvedUrl.driveId,\n\t\titemId: odspResolvedUrl.itemId,\n\t\tfileVersion,\n\t});\n\tif (odspResolvedUrl.dataStorePath !== undefined) {\n\t\tquery.set(\"path\", odspResolvedUrl.dataStorePath);\n\t}\n\tif (odspResolvedUrl.codeHint?.containerPackageName !== undefined) {\n\t\tquery.set(\"containerPackageName\", odspResolvedUrl.codeHint.containerPackageName);\n\t}\n\treturn new OdspDriverUrlResolver().resolve({\n\t\turl: `${odspResolvedUrl.siteUrl}?${query.toString()}`,\n\t});\n}\n\n/**\n * ODSP's point-in-time document service implementation.\n *\n * @remarks Import this function from the dedicated point-in-time entrypoint and inject it through\n * {@link IOdspDocumentServiceFactoryOptions.pointInTimeDocumentServiceImplementation}.\n *\n * @param props - ODSP service dependencies and point-in-time load parameters supplied by the factory.\n * @returns A read-only ODSP document service materialized at the requested sequence number.\n *\n * @legacy @beta\n */\nexport async function createPointInTimeDocumentService(\n\tprops: IOdspPointInTimeDocumentServiceImplementationProps,\n): Promise<IDocumentService> {\n\treturn createPointInTimeDocumentServiceCore(props);\n}\n\n/**\n * Creates the point-in-time service with replaceable dependencies for testing.\n * @internal\n */\nexport async function createPointInTimeDocumentServiceCore(\n\t{\n\t\tresolvedUrl,\n\t\ttargetSequenceNumber,\n\t\tlogger,\n\t\tclientIsSummarizer,\n\t\tpersistedCache,\n\t\tgetStorageToken,\n\t\trequestHeaders,\n\t\tcreateDocumentService,\n\t}: IOdspPointInTimeDocumentServiceImplementationProps,\n\tdependencies: IPointInTimeDocumentServiceDependencies = {},\n): Promise<OdspPointInTimeDocumentService> {\n\tconst odspLogger = createOdspLogger(logger);\n\tconst extLogger = createChildLogger({ logger: odspLogger });\n\tconst odspResolvedUrl = getOdspResolvedUrl(resolvedUrl);\n\tconst cacheAndTracker = createOdspCacheAndTracker(\n\t\tpersistedCache,\n\t\tnew NonPersistentCache(),\n\t\t{\n\t\t\tresolvedUrl: odspResolvedUrl,\n\t\t\tdocId: odspResolvedUrl.hashedDocumentId,\n\t\t\tfileVersion: odspResolvedUrl.fileVersion,\n\t\t},\n\t\textLogger,\n\t\tclientIsSummarizer,\n\t\trequestHeaders,\n\t);\n\n\tconst baseSelectionStartTime = performanceNow();\n\tlet baseResult: Awaited<ReturnType<IOdspVersionManager[\"findBaseForSeq\"]>>;\n\ttry {\n\t\tconst versionManager = await (\n\t\t\tdependencies.createVersionManager ??\n\t\t\t(async (url, versionLogger, epochTracker) =>\n\t\t\t\tcreateVersionManager(\n\t\t\t\t\turl,\n\t\t\t\t\tversionLogger,\n\t\t\t\t\tepochTracker,\n\t\t\t\t\tgetStorageToken,\n\t\t\t\t\trequestHeaders,\n\t\t\t\t))\n\t\t)(odspResolvedUrl, extLogger, cacheAndTracker.epochTracker);\n\t\tbaseResult = await versionManager.findBaseForSeq(targetSequenceNumber);\n\t} catch (error) {\n\t\textLogger.sendErrorEvent(\n\t\t\t{\n\t\t\t\teventName: \"VersionMarkBaseVersionSelection\",\n\t\t\t\toutcome: \"failed\",\n\t\t\t\tduration: performanceNow() - baseSelectionStartTime,\n\t\t\t\ttargetSequenceNumber,\n\t\t\t\terrorType: (error as Partial<{ errorType: string }> | undefined)?.errorType,\n\t\t\t\tavailabilityOutcome: isILoggingError(error)\n\t\t\t\t\t? error.getTelemetryProperties().versionMarkAvailabilityOutcome\n\t\t\t\t\t: undefined,\n\t\t\t},\n\t\t\terror,\n\t\t);\n\t\tthrow error;\n\t}\n\tif (baseResult.kind === \"noBaseVersion\") {\n\t\tconst oldestResolvedSequenceDetail =\n\t\t\tbaseResult.oldestResolvedSeq === undefined\n\t\t\t\t? \"\"\n\t\t\t\t: ` The oldest resolved file version is at sequence number ${baseResult.oldestResolvedSeq}.`;\n\t\tconst error = new UsageError(\n\t\t\t`No ODSP file version is available at or before sequence number ${targetSequenceNumber}.${oldestResolvedSequenceDetail}`,\n\t\t);\n\t\terror.addTelemetryProperties({\n\t\t\tversionMarkAvailabilityOutcome: \"baseVersionMissing\",\n\t\t});\n\t\textLogger.sendErrorEvent({\n\t\t\teventName: \"VersionMarkBaseVersionSelection\",\n\t\t\toutcome: \"failed\",\n\t\t\tduration: performanceNow() - baseSelectionStartTime,\n\t\t\ttargetSequenceNumber,\n\t\t\tavailabilityOutcome: \"baseVersionMissing\",\n\t\t\toldestResolvedSequenceNumber: baseResult.oldestResolvedSeq,\n\t\t\tversionsProbed: baseResult.versionsProbed,\n\t\t\tsequenceNumberFetchCount: baseResult.sequenceNumberFetchCount,\n\t\t\terrorType: error.errorType,\n\t\t});\n\t\tthrow error;\n\t}\n\textLogger.sendPerformanceEvent({\n\t\teventName: \"VersionMarkBaseVersionSelection\",\n\t\toutcome: \"succeeded\",\n\t\tduration: performanceNow() - baseSelectionStartTime,\n\t\tversionsProbed: baseResult.versionsProbed,\n\t\tsequenceNumberFetchCount: baseResult.sequenceNumberFetchCount,\n\t});\n\n\tconst recoverableResolvedUrl = await (dependencies.resolveFileVersion ?? resolveFileVersion)(\n\t\tresolvedUrl,\n\t\tbaseResult.base.versionId,\n\t);\n\tconst recoverableDocumentService = await createDocumentService(\n\t\trecoverableResolvedUrl,\n\t\todspLogger,\n\t\tcacheAndTracker,\n\t\tclientIsSummarizer,\n\t);\n\tlet liveDocumentService: IDocumentService;\n\ttry {\n\t\tliveDocumentService = await createDocumentService(\n\t\t\tresolvedUrl,\n\t\t\todspLogger,\n\t\t\tcacheAndTracker,\n\t\t\tclientIsSummarizer,\n\t\t);\n\t} catch (error) {\n\t\ttry {\n\t\t\trecoverableDocumentService.dispose();\n\t\t} catch (disposeError) {\n\t\t\textLogger.sendErrorEvent(\n\t\t\t\t{\n\t\t\t\t\teventName: \"VersionMarkRecoverableDocumentServiceDisposeError\",\n\t\t\t\t},\n\t\t\t\tdisposeError,\n\t\t\t);\n\t\t}\n\t\tthrow error;\n\t}\n\treturn new OdspPointInTimeDocumentService(\n\t\trecoverableResolvedUrl,\n\t\trecoverableDocumentService,\n\t\tliveDocumentService,\n\t\ttargetSequenceNumber,\n\t);\n}\n"]}
@@ -1 +1 @@
1
- {"version":3,"file":"odspPointInTimeDocumentService.d.ts","sourceRoot":"","sources":["../../src/pointInTimeDriver/odspPointInTimeDocumentService.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,OAAO,EAAE,iBAAiB,EAAE,MAAM,8BAA8B,CAAC;AACjE,OAAO,EAEN,KAAK,OAAO,EACZ,KAAK,wBAAwB,EAC7B,KAAK,4BAA4B,EACjC,KAAK,gBAAgB,EACrB,KAAK,sBAAsB,EAC3B,KAAK,wBAAwB,EAC7B,KAAK,uBAAuB,EAC5B,KAAK,YAAY,EAIjB,MAAM,6CAA6C,CAAC;AA2BrD;;;;;;;;;;;;;;;;;;;;;GAqBG;AACH,qBAAa,8BACZ,SAAQ,iBAAiB,CAAC,sBAAsB,CAChD,YAAW,gBAAgB;aAGV,WAAW,EAAE,YAAY;IAGzC,OAAO,CAAC,QAAQ,CAAC,0BAA0B;IAC3C,OAAO,CAAC,QAAQ,CAAC,mBAAmB;IACpC,OAAO,CAAC,QAAQ,CAAC,oBAAoB;gBALrB,WAAW,EAAE,YAAY,EAGxB,0BAA0B,EAAE,gBAAgB,EAC5C,mBAAmB,EAAE,gBAAgB,EACrC,oBAAoB,EAAE,MAAM;IAQ9C,SAAgB,QAAQ,EAAE,wBAAwB,CAAyB;IAEpE,OAAO,IAAI,IAAI;IAMT,gBAAgB,IAAI,OAAO,CAAC,uBAAuB,CAAC;IAKpD,qBAAqB,IAAI,OAAO,CAAC,4BAA4B,CAAC;IAgB9D,oBAAoB,CAAC,OAAO,EAAE,OAAO,GAAG,OAAO,CAAC,wBAAwB,CAAC;IAStF,OAAO,CAAC,QAAQ,CAAC,qBAAqB,CAEpC;CACF"}
1
+ {"version":3,"file":"odspPointInTimeDocumentService.d.ts","sourceRoot":"","sources":["../../src/pointInTimeDriver/odspPointInTimeDocumentService.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,OAAO,EAAE,iBAAiB,EAAE,MAAM,8BAA8B,CAAC;AACjE,OAAO,EAEN,KAAK,OAAO,EACZ,KAAK,wBAAwB,EAC7B,KAAK,4BAA4B,EACjC,KAAK,gBAAgB,EACrB,KAAK,sBAAsB,EAC3B,KAAK,wBAAwB,EAC7B,KAAK,uBAAuB,EAC5B,KAAK,YAAY,EAIjB,MAAM,6CAA6C,CAAC;AA+CrD;;;;;;;;;;;;;;;;;;;;;GAqBG;AACH,qBAAa,8BACZ,SAAQ,iBAAiB,CAAC,sBAAsB,CAChD,YAAW,gBAAgB;aAGV,WAAW,EAAE,YAAY;IAGzC,OAAO,CAAC,QAAQ,CAAC,0BAA0B;IAC3C,OAAO,CAAC,QAAQ,CAAC,mBAAmB;IACpC,OAAO,CAAC,QAAQ,CAAC,oBAAoB;gBALrB,WAAW,EAAE,YAAY,EAGxB,0BAA0B,EAAE,gBAAgB,EAC5C,mBAAmB,EAAE,gBAAgB,EACrC,oBAAoB,EAAE,MAAM;IAQ9C,SAAgB,QAAQ,EAAE,wBAAwB,CAAyB;IAEpE,OAAO,IAAI,IAAI;IAMT,gBAAgB,IAAI,OAAO,CAAC,uBAAuB,CAAC;IAKpD,qBAAqB,IAAI,OAAO,CAAC,4BAA4B,CAAC;IAmC9D,oBAAoB,CAAC,OAAO,EAAE,OAAO,GAAG,OAAO,CAAC,wBAAwB,CAAC;IAStF,OAAO,CAAC,QAAQ,CAAC,qBAAqB,CAEpC;CACF"}
@@ -8,6 +8,8 @@ exports.OdspPointInTimeDocumentService = void 0;
8
8
  const client_utils_1 = require("@fluid-internal/client-utils");
9
9
  const internal_1 = require("@fluidframework/driver-definitions/internal");
10
10
  const internal_2 = require("@fluidframework/driver-utils/internal");
11
+ const internal_3 = require("@fluidframework/odsp-driver-definitions/internal");
12
+ const internal_4 = require("@fluidframework/telemetry-utils/internal");
11
13
  /**
12
14
  * Forces point-in-time snapshot reads to bypass caches while forwarding all other storage operations
13
15
  * to the selected historical document service.
@@ -25,6 +27,21 @@ class PointInTimeDocumentStorageService extends internal_2.DocumentStorageServic
25
27
  return super.getVersions(versionId, count, scenarioName, internal_1.FetchSource.noCache);
26
28
  }
27
29
  }
30
+ function annotateMissingOps(error) {
31
+ if (!(0, internal_4.isFluidError)(error)) {
32
+ return;
33
+ }
34
+ // Attach a stable Version Mark availability classification only when the driver error
35
+ // confirms that the required historical ops are unavailable.
36
+ const historyIsUnavailable = error.errorType === internal_3.OdspErrorTypes.cannotCatchUp ||
37
+ (error.errorType === internal_3.OdspErrorTypes.genericNetworkError &&
38
+ error.getTelemetryProperties().opsFetchFailure === "tooManyRetries");
39
+ if (historyIsUnavailable) {
40
+ error.addTelemetryProperties({
41
+ versionMarkAvailabilityOutcome: "missingOps",
42
+ });
43
+ }
44
+ }
28
45
  /**
29
46
  * A read-only document service that materializes a document at a target sequence number by combining
30
47
  * a recoverable snapshot with a bounded replay of live ops.
@@ -80,7 +97,29 @@ class OdspPointInTimeDocumentService extends client_utils_1.TypedEventEmitter {
80
97
  // The exclusive upper bound needed to include the target op itself.
81
98
  const boundedTo = this.targetSequenceNumber + 1;
82
99
  return {
83
- fetchMessages: (from, to, abortSignal, cachedOnly, fetchReason) => liveDeltaStorage.fetchMessages(from, to === undefined ? boundedTo : Math.min(to, boundedTo), abortSignal, cachedOnly, fetchReason),
100
+ fetchMessages: (from, to, abortSignal, cachedOnly, fetchReason) => {
101
+ try {
102
+ const stream = liveDeltaStorage.fetchMessages(from, to === undefined ? boundedTo : Math.min(to, boundedTo), abortSignal, cachedOnly, fetchReason);
103
+ return {
104
+ read: async () => {
105
+ try {
106
+ return await stream.read();
107
+ }
108
+ catch (error) {
109
+ // The bounded replay owns the conclusion that these specific failures mean
110
+ // the bridge to the target cannot be materialized. Preserve the driver's raw
111
+ // errorType while attaching the stable Version Mark availability outcome.
112
+ annotateMissingOps(error);
113
+ throw error;
114
+ }
115
+ },
116
+ };
117
+ }
118
+ catch (error) {
119
+ annotateMissingOps(error);
120
+ throw error;
121
+ }
122
+ },
84
123
  };
85
124
  }
86
125
  async connectToDeltaStream(_client) {
@@ -1 +1 @@
1
- {"version":3,"file":"odspPointInTimeDocumentService.js","sourceRoot":"","sources":["../../src/pointInTimeDriver/odspPointInTimeDocumentService.ts"],"names":[],"mappings":";AAAA;;;GAGG;;;AAEH,+DAAiE;AACjE,0EAaqD;AACrD,oEAAoF;AAEpF;;;GAGG;AACH,MAAM,iCAAkC,SAAQ,sCAA2B;IAC1D,KAAK,CAAC,WAAW,CAChC,oBAA4C;QAE5C,OAAO,KAAK,CAAC,WAAW,CAAC;YACxB,GAAG,oBAAoB;YACvB,WAAW,EAAE,sBAAW,CAAC,OAAO;SAChC,CAAC,CAAC;IACJ,CAAC;IAEe,KAAK,CAAC,WAAW;IAChC,iHAAiH;IACjH,SAAwB,EACxB,KAAa,EACb,YAAqB;QAErB,OAAO,KAAK,CAAC,WAAW,CAAC,SAAS,EAAE,KAAK,EAAE,YAAY,EAAE,sBAAW,CAAC,OAAO,CAAC,CAAC;IAC/E,CAAC;CACD;AAED;;;;;;;;;;;;;;;;;;;;;GAqBG;AACH,MAAa,8BACZ,SAAQ,gCAAyC;IAIhC;IAGC;IACA;IACA;IANlB,YACiB,WAAyB;IACzC,4FAA4F;IAC5F,sFAAsF;IACrE,0BAA4C,EAC5C,mBAAqC,EACrC,oBAA4B;QAE7C,KAAK,EAAE,CAAC;QAPQ,gBAAW,GAAX,WAAW,CAAc;QAGxB,+BAA0B,GAA1B,0BAA0B,CAAkB;QAC5C,wBAAmB,GAAnB,mBAAmB,CAAkB;QACrC,yBAAoB,GAApB,oBAAoB,CAAQ;QAG7C,IAAI,CAAC,mBAAmB,CAAC,EAAE,CAAC,gBAAgB,EAAE,IAAI,CAAC,qBAAqB,CAAC,CAAC;IAC3E,CAAC;IAED,+FAA+F;IAC/F,qEAAqE;IACrD,QAAQ,GAA6B,EAAE,WAAW,EAAE,IAAI,EAAE,CAAC;IAEpE,OAAO;QACb,IAAI,CAAC,mBAAmB,CAAC,GAAG,CAAC,gBAAgB,EAAE,IAAI,CAAC,qBAAqB,CAAC,CAAC;QAC3E,IAAI,CAAC,0BAA0B,CAAC,OAAO,EAAE,CAAC;QAC1C,IAAI,CAAC,mBAAmB,CAAC,OAAO,EAAE,CAAC;IACpC,CAAC;IAEM,KAAK,CAAC,gBAAgB;QAC5B,MAAM,OAAO,GAAG,MAAM,IAAI,CAAC,0BAA0B,CAAC,gBAAgB,EAAE,CAAC;QACzE,OAAO,IAAI,iCAAiC,CAAC,OAAO,CAAC,CAAC;IACvD,CAAC;IAEM,KAAK,CAAC,qBAAqB;QACjC,MAAM,gBAAgB,GAAG,MAAM,IAAI,CAAC,mBAAmB,CAAC,qBAAqB,EAAE,CAAC;QAChF,oEAAoE;QACpE,MAAM,SAAS,GAAG,IAAI,CAAC,oBAAoB,GAAG,CAAC,CAAC;QAChD,OAAO;YACN,aAAa,EAAE,CAAC,IAAI,EAAE,EAAE,EAAE,WAAW,EAAE,UAAU,EAAE,WAAW,EAAE,EAAE,CACjE,gBAAgB,CAAC,aAAa,CAC7B,IAAI,EACJ,EAAE,KAAK,SAAS,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC,IAAI,CAAC,GAAG,CAAC,EAAE,EAAE,SAAS,CAAC,EACtD,WAAW,EACX,UAAU,EACV,WAAW,CACX;SACF,CAAC;IACH,CAAC;IAEM,KAAK,CAAC,oBAAoB,CAAC,OAAgB;QACjD,0FAA0F;QAC1F,yFAAyF;QACzF,8DAA8D;QAC9D,MAAM,IAAI,KAAK,CACd,2FAA2F,CAC3F,CAAC;IACH,CAAC;IAEgB,qBAAqB,GAAG,CAAC,QAAgC,EAAQ,EAAE;QACnF,IAAI,CAAC,IAAI,CAAC,gBAAgB,EAAE,QAAQ,CAAC,CAAC;IACvC,CAAC,CAAC;CACF;AA3DD,wEA2DC","sourcesContent":["/*!\n * Copyright (c) Microsoft Corporation and contributors. All rights reserved.\n * Licensed under the MIT License.\n */\n\nimport { TypedEventEmitter } from \"@fluid-internal/client-utils\";\nimport {\n\tFetchSource,\n\ttype IClient,\n\ttype IDocumentDeltaConnection,\n\ttype IDocumentDeltaStorageService,\n\ttype IDocumentService,\n\ttype IDocumentServiceEvents,\n\ttype IDocumentServicePolicies,\n\ttype IDocumentStorageService,\n\ttype IResolvedUrl,\n\ttype ISnapshot,\n\ttype ISnapshotFetchOptions,\n\ttype IVersion,\n} from \"@fluidframework/driver-definitions/internal\";\nimport { DocumentStorageServiceProxy } from \"@fluidframework/driver-utils/internal\";\n\n/**\n * Forces point-in-time snapshot reads to bypass caches while forwarding all other storage operations\n * to the selected historical document service.\n */\nclass PointInTimeDocumentStorageService extends DocumentStorageServiceProxy {\n\tpublic override async getSnapshot(\n\t\tsnapshotFetchOptions?: ISnapshotFetchOptions,\n\t): Promise<ISnapshot> {\n\t\treturn super.getSnapshot({\n\t\t\t...snapshotFetchOptions,\n\t\t\tfetchSource: FetchSource.noCache,\n\t\t});\n\t}\n\n\tpublic override async getVersions(\n\t\t// eslint-disable-next-line @rushstack/no-new-null -- IDocumentStorageService is a legacy API that requires null.\n\t\tversionId: string | null,\n\t\tcount: number,\n\t\tscenarioName?: string,\n\t): Promise<IVersion[]> {\n\t\treturn super.getVersions(versionId, count, scenarioName, FetchSource.noCache);\n\t}\n}\n\n/**\n * A read-only document service that materializes a document at a target sequence number by combining\n * a recoverable snapshot with a bounded replay of live ops.\n *\n * @remarks\n * Storage (the snapshot) is served from the closest file version at or before the target sequence\n * number. The ops needed to advance from that snapshot to the target are read from the live\n * document's delta storage, bounded so that no op past the target is ever fetched.\n *\n * The service advertises the {@link IDocumentServicePolicies.storageOnly} policy. This reuses the\n * loader's \"frozen\" load mechanism: the connection manager synthesizes a read-only frozen delta\n * stream instead of opening a live socket, and forces the container read-only. The delta manager\n * still catches up from the snapshot's sequence number through delta storage, which is exactly the\n * bounded replay we want. As a result no live delta-stream connection is ever established.\n *\n * Op availability is enforced by the delta storage stack itself: it validates that fetched batches\n * are contiguous from the requested start, keeps requesting until the bounded range is fully\n * delivered, and fails the fetch if the ops never materialize. So a stream that completes has\n * necessarily served the whole bridge, and no additional checks are needed here.\n *\n * @internal\n */\nexport class OdspPointInTimeDocumentService\n\textends TypedEventEmitter<IDocumentServiceEvents>\n\timplements IDocumentService\n{\n\tpublic constructor(\n\t\tpublic readonly resolvedUrl: IResolvedUrl,\n\t\t// Serves the snapshot: a read-only document service bound to the closest file version at or\n\t\t// before the target sequence number. Its storage is the base we replay ops on top of.\n\t\tprivate readonly recoverableDocumentService: IDocumentService,\n\t\tprivate readonly liveDocumentService: IDocumentService,\n\t\tprivate readonly targetSequenceNumber: number,\n\t) {\n\t\tsuper();\n\t\tthis.liveDocumentService.on(\"metadataUpdate\", this.metadataUpdateHandler);\n\t}\n\n\t// storageOnly makes the connection manager synthesize a read-only frozen delta stream (no live\n\t// socket) and force the container read-only - see the class remarks.\n\tpublic readonly policies: IDocumentServicePolicies = { storageOnly: true };\n\n\tpublic dispose(): void {\n\t\tthis.liveDocumentService.off(\"metadataUpdate\", this.metadataUpdateHandler);\n\t\tthis.recoverableDocumentService.dispose();\n\t\tthis.liveDocumentService.dispose();\n\t}\n\n\tpublic async connectToStorage(): Promise<IDocumentStorageService> {\n\t\tconst storage = await this.recoverableDocumentService.connectToStorage();\n\t\treturn new PointInTimeDocumentStorageService(storage);\n\t}\n\n\tpublic async connectToDeltaStorage(): Promise<IDocumentDeltaStorageService> {\n\t\tconst liveDeltaStorage = await this.liveDocumentService.connectToDeltaStorage();\n\t\t// The exclusive upper bound needed to include the target op itself.\n\t\tconst boundedTo = this.targetSequenceNumber + 1;\n\t\treturn {\n\t\t\tfetchMessages: (from, to, abortSignal, cachedOnly, fetchReason) =>\n\t\t\t\tliveDeltaStorage.fetchMessages(\n\t\t\t\t\tfrom,\n\t\t\t\t\tto === undefined ? boundedTo : Math.min(to, boundedTo),\n\t\t\t\t\tabortSignal,\n\t\t\t\t\tcachedOnly,\n\t\t\t\t\tfetchReason,\n\t\t\t\t),\n\t\t};\n\t}\n\n\tpublic async connectToDeltaStream(_client: IClient): Promise<IDocumentDeltaConnection> {\n\t\t// Unreachable under normal flow: the connection manager short-circuits on the storageOnly\n\t\t// policy and synthesizes a frozen delta stream before ever calling connectToDeltaStream.\n\t\t// Reaching here indicates a regression of that short-circuit.\n\t\tthrow new Error(\n\t\t\t\"OdspPointInTimeDocumentService is storage-only; connectToDeltaStream should not be called\",\n\t\t);\n\t}\n\n\tprivate readonly metadataUpdateHandler = (metadata: Record<string, string>): void => {\n\t\tthis.emit(\"metadataUpdate\", metadata);\n\t};\n}\n"]}
1
+ {"version":3,"file":"odspPointInTimeDocumentService.js","sourceRoot":"","sources":["../../src/pointInTimeDriver/odspPointInTimeDocumentService.ts"],"names":[],"mappings":";AAAA;;;GAGG;;;AAEH,+DAAiE;AACjE,0EAaqD;AACrD,oEAAoF;AACpF,+EAAkF;AAClF,uEAAwE;AAExE;;;GAGG;AACH,MAAM,iCAAkC,SAAQ,sCAA2B;IAC1D,KAAK,CAAC,WAAW,CAChC,oBAA4C;QAE5C,OAAO,KAAK,CAAC,WAAW,CAAC;YACxB,GAAG,oBAAoB;YACvB,WAAW,EAAE,sBAAW,CAAC,OAAO;SAChC,CAAC,CAAC;IACJ,CAAC;IAEe,KAAK,CAAC,WAAW;IAChC,iHAAiH;IACjH,SAAwB,EACxB,KAAa,EACb,YAAqB;QAErB,OAAO,KAAK,CAAC,WAAW,CAAC,SAAS,EAAE,KAAK,EAAE,YAAY,EAAE,sBAAW,CAAC,OAAO,CAAC,CAAC;IAC/E,CAAC;CACD;AAED,SAAS,kBAAkB,CAAC,KAAc;IACzC,IAAI,CAAC,IAAA,uBAAY,EAAC,KAAK,CAAC,EAAE,CAAC;QAC1B,OAAO;IACR,CAAC;IAED,sFAAsF;IACtF,6DAA6D;IAC7D,MAAM,oBAAoB,GACzB,KAAK,CAAC,SAAS,KAAK,yBAAc,CAAC,aAAa;QAChD,CAAC,KAAK,CAAC,SAAS,KAAK,yBAAc,CAAC,mBAAmB;YACtD,KAAK,CAAC,sBAAsB,EAAE,CAAC,eAAe,KAAK,gBAAgB,CAAC,CAAC;IACvE,IAAI,oBAAoB,EAAE,CAAC;QAC1B,KAAK,CAAC,sBAAsB,CAAC;YAC5B,8BAA8B,EAAE,YAAY;SAC5C,CAAC,CAAC;IACJ,CAAC;AACF,CAAC;AAED;;;;;;;;;;;;;;;;;;;;;GAqBG;AACH,MAAa,8BACZ,SAAQ,gCAAyC;IAIhC;IAGC;IACA;IACA;IANlB,YACiB,WAAyB;IACzC,4FAA4F;IAC5F,sFAAsF;IACrE,0BAA4C,EAC5C,mBAAqC,EACrC,oBAA4B;QAE7C,KAAK,EAAE,CAAC;QAPQ,gBAAW,GAAX,WAAW,CAAc;QAGxB,+BAA0B,GAA1B,0BAA0B,CAAkB;QAC5C,wBAAmB,GAAnB,mBAAmB,CAAkB;QACrC,yBAAoB,GAApB,oBAAoB,CAAQ;QAG7C,IAAI,CAAC,mBAAmB,CAAC,EAAE,CAAC,gBAAgB,EAAE,IAAI,CAAC,qBAAqB,CAAC,CAAC;IAC3E,CAAC;IAED,+FAA+F;IAC/F,qEAAqE;IACrD,QAAQ,GAA6B,EAAE,WAAW,EAAE,IAAI,EAAE,CAAC;IAEpE,OAAO;QACb,IAAI,CAAC,mBAAmB,CAAC,GAAG,CAAC,gBAAgB,EAAE,IAAI,CAAC,qBAAqB,CAAC,CAAC;QAC3E,IAAI,CAAC,0BAA0B,CAAC,OAAO,EAAE,CAAC;QAC1C,IAAI,CAAC,mBAAmB,CAAC,OAAO,EAAE,CAAC;IACpC,CAAC;IAEM,KAAK,CAAC,gBAAgB;QAC5B,MAAM,OAAO,GAAG,MAAM,IAAI,CAAC,0BAA0B,CAAC,gBAAgB,EAAE,CAAC;QACzE,OAAO,IAAI,iCAAiC,CAAC,OAAO,CAAC,CAAC;IACvD,CAAC;IAEM,KAAK,CAAC,qBAAqB;QACjC,MAAM,gBAAgB,GAAG,MAAM,IAAI,CAAC,mBAAmB,CAAC,qBAAqB,EAAE,CAAC;QAChF,oEAAoE;QACpE,MAAM,SAAS,GAAG,IAAI,CAAC,oBAAoB,GAAG,CAAC,CAAC;QAChD,OAAO;YACN,aAAa,EAAE,CAAC,IAAI,EAAE,EAAE,EAAE,WAAW,EAAE,UAAU,EAAE,WAAW,EAAE,EAAE;gBACjE,IAAI,CAAC;oBACJ,MAAM,MAAM,GAAG,gBAAgB,CAAC,aAAa,CAC5C,IAAI,EACJ,EAAE,KAAK,SAAS,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC,IAAI,CAAC,GAAG,CAAC,EAAE,EAAE,SAAS,CAAC,EACtD,WAAW,EACX,UAAU,EACV,WAAW,CACX,CAAC;oBACF,OAAO;wBACN,IAAI,EAAE,KAAK,IAAI,EAAE;4BAChB,IAAI,CAAC;gCACJ,OAAO,MAAM,MAAM,CAAC,IAAI,EAAE,CAAC;4BAC5B,CAAC;4BAAC,OAAO,KAAK,EAAE,CAAC;gCAChB,2EAA2E;gCAC3E,6EAA6E;gCAC7E,0EAA0E;gCAC1E,kBAAkB,CAAC,KAAK,CAAC,CAAC;gCAC1B,MAAM,KAAK,CAAC;4BACb,CAAC;wBACF,CAAC;qBACD,CAAC;gBACH,CAAC;gBAAC,OAAO,KAAK,EAAE,CAAC;oBAChB,kBAAkB,CAAC,KAAK,CAAC,CAAC;oBAC1B,MAAM,KAAK,CAAC;gBACb,CAAC;YACF,CAAC;SACD,CAAC;IACH,CAAC;IAEM,KAAK,CAAC,oBAAoB,CAAC,OAAgB;QACjD,0FAA0F;QAC1F,yFAAyF;QACzF,8DAA8D;QAC9D,MAAM,IAAI,KAAK,CACd,2FAA2F,CAC3F,CAAC;IACH,CAAC;IAEgB,qBAAqB,GAAG,CAAC,QAAgC,EAAQ,EAAE;QACnF,IAAI,CAAC,IAAI,CAAC,gBAAgB,EAAE,QAAQ,CAAC,CAAC;IACvC,CAAC,CAAC;CACF;AA9ED,wEA8EC","sourcesContent":["/*!\n * Copyright (c) Microsoft Corporation and contributors. All rights reserved.\n * Licensed under the MIT License.\n */\n\nimport { TypedEventEmitter } from \"@fluid-internal/client-utils\";\nimport {\n\tFetchSource,\n\ttype IClient,\n\ttype IDocumentDeltaConnection,\n\ttype IDocumentDeltaStorageService,\n\ttype IDocumentService,\n\ttype IDocumentServiceEvents,\n\ttype IDocumentServicePolicies,\n\ttype IDocumentStorageService,\n\ttype IResolvedUrl,\n\ttype ISnapshot,\n\ttype ISnapshotFetchOptions,\n\ttype IVersion,\n} from \"@fluidframework/driver-definitions/internal\";\nimport { DocumentStorageServiceProxy } from \"@fluidframework/driver-utils/internal\";\nimport { OdspErrorTypes } from \"@fluidframework/odsp-driver-definitions/internal\";\nimport { isFluidError } from \"@fluidframework/telemetry-utils/internal\";\n\n/**\n * Forces point-in-time snapshot reads to bypass caches while forwarding all other storage operations\n * to the selected historical document service.\n */\nclass PointInTimeDocumentStorageService extends DocumentStorageServiceProxy {\n\tpublic override async getSnapshot(\n\t\tsnapshotFetchOptions?: ISnapshotFetchOptions,\n\t): Promise<ISnapshot> {\n\t\treturn super.getSnapshot({\n\t\t\t...snapshotFetchOptions,\n\t\t\tfetchSource: FetchSource.noCache,\n\t\t});\n\t}\n\n\tpublic override async getVersions(\n\t\t// eslint-disable-next-line @rushstack/no-new-null -- IDocumentStorageService is a legacy API that requires null.\n\t\tversionId: string | null,\n\t\tcount: number,\n\t\tscenarioName?: string,\n\t): Promise<IVersion[]> {\n\t\treturn super.getVersions(versionId, count, scenarioName, FetchSource.noCache);\n\t}\n}\n\nfunction annotateMissingOps(error: unknown): void {\n\tif (!isFluidError(error)) {\n\t\treturn;\n\t}\n\n\t// Attach a stable Version Mark availability classification only when the driver error\n\t// confirms that the required historical ops are unavailable.\n\tconst historyIsUnavailable =\n\t\terror.errorType === OdspErrorTypes.cannotCatchUp ||\n\t\t(error.errorType === OdspErrorTypes.genericNetworkError &&\n\t\t\terror.getTelemetryProperties().opsFetchFailure === \"tooManyRetries\");\n\tif (historyIsUnavailable) {\n\t\terror.addTelemetryProperties({\n\t\t\tversionMarkAvailabilityOutcome: \"missingOps\",\n\t\t});\n\t}\n}\n\n/**\n * A read-only document service that materializes a document at a target sequence number by combining\n * a recoverable snapshot with a bounded replay of live ops.\n *\n * @remarks\n * Storage (the snapshot) is served from the closest file version at or before the target sequence\n * number. The ops needed to advance from that snapshot to the target are read from the live\n * document's delta storage, bounded so that no op past the target is ever fetched.\n *\n * The service advertises the {@link IDocumentServicePolicies.storageOnly} policy. This reuses the\n * loader's \"frozen\" load mechanism: the connection manager synthesizes a read-only frozen delta\n * stream instead of opening a live socket, and forces the container read-only. The delta manager\n * still catches up from the snapshot's sequence number through delta storage, which is exactly the\n * bounded replay we want. As a result no live delta-stream connection is ever established.\n *\n * Op availability is enforced by the delta storage stack itself: it validates that fetched batches\n * are contiguous from the requested start, keeps requesting until the bounded range is fully\n * delivered, and fails the fetch if the ops never materialize. So a stream that completes has\n * necessarily served the whole bridge, and no additional checks are needed here.\n *\n * @internal\n */\nexport class OdspPointInTimeDocumentService\n\textends TypedEventEmitter<IDocumentServiceEvents>\n\timplements IDocumentService\n{\n\tpublic constructor(\n\t\tpublic readonly resolvedUrl: IResolvedUrl,\n\t\t// Serves the snapshot: a read-only document service bound to the closest file version at or\n\t\t// before the target sequence number. Its storage is the base we replay ops on top of.\n\t\tprivate readonly recoverableDocumentService: IDocumentService,\n\t\tprivate readonly liveDocumentService: IDocumentService,\n\t\tprivate readonly targetSequenceNumber: number,\n\t) {\n\t\tsuper();\n\t\tthis.liveDocumentService.on(\"metadataUpdate\", this.metadataUpdateHandler);\n\t}\n\n\t// storageOnly makes the connection manager synthesize a read-only frozen delta stream (no live\n\t// socket) and force the container read-only - see the class remarks.\n\tpublic readonly policies: IDocumentServicePolicies = { storageOnly: true };\n\n\tpublic dispose(): void {\n\t\tthis.liveDocumentService.off(\"metadataUpdate\", this.metadataUpdateHandler);\n\t\tthis.recoverableDocumentService.dispose();\n\t\tthis.liveDocumentService.dispose();\n\t}\n\n\tpublic async connectToStorage(): Promise<IDocumentStorageService> {\n\t\tconst storage = await this.recoverableDocumentService.connectToStorage();\n\t\treturn new PointInTimeDocumentStorageService(storage);\n\t}\n\n\tpublic async connectToDeltaStorage(): Promise<IDocumentDeltaStorageService> {\n\t\tconst liveDeltaStorage = await this.liveDocumentService.connectToDeltaStorage();\n\t\t// The exclusive upper bound needed to include the target op itself.\n\t\tconst boundedTo = this.targetSequenceNumber + 1;\n\t\treturn {\n\t\t\tfetchMessages: (from, to, abortSignal, cachedOnly, fetchReason) => {\n\t\t\t\ttry {\n\t\t\t\t\tconst stream = liveDeltaStorage.fetchMessages(\n\t\t\t\t\t\tfrom,\n\t\t\t\t\t\tto === undefined ? boundedTo : Math.min(to, boundedTo),\n\t\t\t\t\t\tabortSignal,\n\t\t\t\t\t\tcachedOnly,\n\t\t\t\t\t\tfetchReason,\n\t\t\t\t\t);\n\t\t\t\t\treturn {\n\t\t\t\t\t\tread: async () => {\n\t\t\t\t\t\t\ttry {\n\t\t\t\t\t\t\t\treturn await stream.read();\n\t\t\t\t\t\t\t} catch (error) {\n\t\t\t\t\t\t\t\t// The bounded replay owns the conclusion that these specific failures mean\n\t\t\t\t\t\t\t\t// the bridge to the target cannot be materialized. Preserve the driver's raw\n\t\t\t\t\t\t\t\t// errorType while attaching the stable Version Mark availability outcome.\n\t\t\t\t\t\t\t\tannotateMissingOps(error);\n\t\t\t\t\t\t\t\tthrow error;\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t},\n\t\t\t\t\t};\n\t\t\t\t} catch (error) {\n\t\t\t\t\tannotateMissingOps(error);\n\t\t\t\t\tthrow error;\n\t\t\t\t}\n\t\t\t},\n\t\t};\n\t}\n\n\tpublic async connectToDeltaStream(_client: IClient): Promise<IDocumentDeltaConnection> {\n\t\t// Unreachable under normal flow: the connection manager short-circuits on the storageOnly\n\t\t// policy and synthesizes a frozen delta stream before ever calling connectToDeltaStream.\n\t\t// Reaching here indicates a regression of that short-circuit.\n\t\tthrow new Error(\n\t\t\t\"OdspPointInTimeDocumentService is storage-only; connectToDeltaStream should not be called\",\n\t\t);\n\t}\n\n\tprivate readonly metadataUpdateHandler = (metadata: Record<string, string>): void => {\n\t\tthis.emit(\"metadataUpdate\", metadata);\n\t};\n}\n"]}
@@ -27,6 +27,10 @@ export type BaseForSeq = {
27
27
  /** A recoverable version with `sequenceNumber <= target` was found. */
28
28
  readonly kind: "found";
29
29
  readonly base: ResolvedVersion;
30
+ /** Number of sealed version candidates examined before selecting the base. */
31
+ readonly versionsProbed: number;
32
+ /** Number of candidate sequence numbers that required an uncached ODSP fetch. */
33
+ readonly sequenceNumberFetchCount: number;
30
34
  } | {
31
35
  /**
32
36
  * No sealed version has `sequenceNumber <= target` — the target predates retained history, or
@@ -35,6 +39,10 @@ export type BaseForSeq = {
35
39
  readonly kind: "noBaseVersion";
36
40
  /** The oldest sequence number that was resolved while searching, if any. */
37
41
  readonly oldestResolvedSeq?: number;
42
+ /** Number of sealed version candidates examined before exhausting retained history. */
43
+ readonly versionsProbed: number;
44
+ /** Number of candidate sequence numbers that required an uncached ODSP fetch. */
45
+ readonly sequenceNumberFetchCount: number;
38
46
  };
39
47
  /**
40
48
  * Selects the file version to use as the base for loading or replaying to a target sequence number.
@@ -1 +1 @@
1
- {"version":3,"file":"odspVersionManager.d.ts","sourceRoot":"","sources":["../../src/odspVersionManager/odspVersionManager.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAgBH,OAAO,EAEN,KAAK,2BAA2B,EAChC,KAAK,kBAAkB,EACvB,KAAK,uBAAuB,EAC5B,MAAM,6BAA6B,CAAC;AAKrC,YAAY,EAAE,kBAAkB,EAAE,uBAAuB,EAAE,MAAM,6BAA6B,CAAC;AAE/F;;GAEG;AACH,MAAM,WAAW,eAAgB,SAAQ,kBAAkB;IAC1D;;OAEG;IACH,QAAQ,CAAC,cAAc,EAAE,MAAM,CAAC;CAChC;AAED;;;;;;;;;GASG;AACH,MAAM,MAAM,UAAU,GACnB;IACA,uEAAuE;IACvE,QAAQ,CAAC,IAAI,EAAE,OAAO,CAAC;IACvB,QAAQ,CAAC,IAAI,EAAE,eAAe,CAAC;CAC9B,GACD;IACA;;;OAGG;IACH,QAAQ,CAAC,IAAI,EAAE,eAAe,CAAC;IAC/B,4EAA4E;IAC5E,QAAQ,CAAC,iBAAiB,CAAC,EAAE,MAAM,CAAC;CACnC,CAAC;AAEL;;GAEG;AACH,MAAM,WAAW,mBAAmB;IACnC;;;;;;;;;OASG;IACH,cAAc,CAAC,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,UAAU,CAAC,CAAC;CACpD;AAED;;;;;GAKG;AAGH,qBAAa,kBAAmB,YAAW,mBAAmB;IAQ1C,OAAO,CAAC,QAAQ,CAAC,OAAO;IAL3C,OAAO,CAAC,QAAQ,CAAC,QAAQ,CAAsC;IAG/D,OAAO,CAAC,QAAQ,CAAC,UAAU,CAAkD;gBAEzC,OAAO,EAAE,uBAAuB;IAEvD,cAAc,CAAC,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,UAAU,CAAC;YA2BlD,oBAAoB;IAqCrB,YAAY,IAAI,OAAO,CAAC,eAAe,EAAE,CAAC;YAiBzC,UAAU;YAQV,mBAAmB;CAOjC;AAED;;GAEG;AACH,wBAAgB,wBAAwB,CACvC,KAAK,EAAE,2BAA2B,GAChC,mBAAmB,CAErB"}
1
+ {"version":3,"file":"odspVersionManager.d.ts","sourceRoot":"","sources":["../../src/odspVersionManager/odspVersionManager.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAgBH,OAAO,EAEN,KAAK,2BAA2B,EAChC,KAAK,kBAAkB,EACvB,KAAK,uBAAuB,EAC5B,MAAM,6BAA6B,CAAC;AAKrC,YAAY,EAAE,kBAAkB,EAAE,uBAAuB,EAAE,MAAM,6BAA6B,CAAC;AAE/F;;GAEG;AACH,MAAM,WAAW,eAAgB,SAAQ,kBAAkB;IAC1D;;OAEG;IACH,QAAQ,CAAC,cAAc,EAAE,MAAM,CAAC;CAChC;AAED;;;;;;;;;GASG;AACH,MAAM,MAAM,UAAU,GACnB;IACA,uEAAuE;IACvE,QAAQ,CAAC,IAAI,EAAE,OAAO,CAAC;IACvB,QAAQ,CAAC,IAAI,EAAE,eAAe,CAAC;IAC/B,8EAA8E;IAC9E,QAAQ,CAAC,cAAc,EAAE,MAAM,CAAC;IAChC,iFAAiF;IACjF,QAAQ,CAAC,wBAAwB,EAAE,MAAM,CAAC;CACzC,GACD;IACA;;;OAGG;IACH,QAAQ,CAAC,IAAI,EAAE,eAAe,CAAC;IAC/B,4EAA4E;IAC5E,QAAQ,CAAC,iBAAiB,CAAC,EAAE,MAAM,CAAC;IACpC,uFAAuF;IACvF,QAAQ,CAAC,cAAc,EAAE,MAAM,CAAC;IAChC,iFAAiF;IACjF,QAAQ,CAAC,wBAAwB,EAAE,MAAM,CAAC;CACzC,CAAC;AAEL;;GAEG;AACH,MAAM,WAAW,mBAAmB;IACnC;;;;;;;;;OASG;IACH,cAAc,CAAC,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,UAAU,CAAC,CAAC;CACpD;AAED;;;;;GAKG;AAGH,qBAAa,kBAAmB,YAAW,mBAAmB;IAQ1C,OAAO,CAAC,QAAQ,CAAC,OAAO;IAL3C,OAAO,CAAC,QAAQ,CAAC,QAAQ,CAAsC;IAG/D,OAAO,CAAC,QAAQ,CAAC,UAAU,CAAkD;gBAEzC,OAAO,EAAE,uBAAuB;IAEvD,cAAc,CAAC,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,UAAU,CAAC;YAqClD,oBAAoB;IAsCrB,YAAY,IAAI,OAAO,CAAC,eAAe,EAAE,CAAC;YAiBzC,UAAU;YASV,mBAAmB;CAOjC;AAED;;GAEG;AACH,wBAAgB,wBAAwB,CACvC,KAAK,EAAE,2BAA2B,GAChC,mBAAmB,CAErB"}
@@ -42,8 +42,13 @@ export class OdspVersionManager {
42
42
  // base — or noBaseVersion, reporting the oldest sequence number seen.
43
43
  const candidates = versions.slice(1);
44
44
  let oldestResolvedSeq;
45
+ let versionsProbed = 0;
46
+ let sequenceNumberFetchCount = 0;
45
47
  for (const version of candidates) {
46
- const sequenceNumber = await this.resolveSeq(version.versionId);
48
+ versionsProbed++;
49
+ const sequenceNumber = await this.resolveSeq(version.versionId, () => {
50
+ sequenceNumberFetchCount++;
51
+ });
47
52
  oldestResolvedSeq =
48
53
  oldestResolvedSeq === undefined
49
54
  ? sequenceNumber
@@ -52,10 +57,15 @@ export class OdspVersionManager {
52
57
  const base = { ...version, sequenceNumber };
53
58
  // Confirm the chosen base shares the live document's lineage before handing it back
54
59
  await this.validateLineageEpoch(base);
55
- return { kind: "found", base };
60
+ return { kind: "found", base, versionsProbed, sequenceNumberFetchCount };
56
61
  }
57
62
  }
58
- return { kind: "noBaseVersion", oldestResolvedSeq };
63
+ return {
64
+ kind: "noBaseVersion",
65
+ oldestResolvedSeq,
66
+ versionsProbed,
67
+ sequenceNumberFetchCount,
68
+ };
59
69
  }
60
70
  async validateLineageEpoch(base) {
61
71
  // The live document's epoch can change (a restore or download-and-reupload bumps it), so it is
@@ -82,6 +92,7 @@ export class OdspVersionManager {
82
92
  driverVersion,
83
93
  serverEpoch: liveEpoch,
84
94
  clientEpoch: baseEpoch,
95
+ versionMarkAvailabilityOutcome: "lineageMismatch",
85
96
  });
86
97
  }
87
98
  }
@@ -98,10 +109,13 @@ export class OdspVersionManager {
98
109
  : await this.resolveSeq(version.versionId),
99
110
  })));
100
111
  }
101
- async resolveSeq(versionId) {
112
+ async resolveSeq(versionId, onCacheMiss) {
102
113
  // Cached indefinitely (a sealed version's number is fixed); concurrent calls coalesce and a failed
103
114
  // resolution is evicted so a later call retries.
104
- return this.seqCache.addOrGet(versionId, async () => this.fetcher.resolveSequenceNumber(versionId));
115
+ return this.seqCache.addOrGet(versionId, async () => {
116
+ onCacheMiss?.();
117
+ return this.fetcher.resolveSequenceNumber(versionId);
118
+ });
105
119
  }
106
120
  async resolveVersionEpoch(versionId) {
107
121
  // Cached like resolveSeq (a sealed version's epoch is fixed). The live document's epoch is read
@@ -1 +1 @@
1
- {"version":3,"file":"odspVersionManager.js","sourceRoot":"","sources":["../../src/odspVersionManager/odspVersionManager.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/**\n * Selects the ODSP file version whose snapshot sits at or before a target Fluid sequence number —\n * the base to load or replay from when materializing a document at a point in time.\n *\n * The selection logic depends on an injected {@link IOdspFileVersionFetcher}, so it is independent of\n * how versions are enumerated and resolved (real ODSP, a test double, or an alternative backend).\n */\n\nimport { PromiseCache } from \"@fluidframework/core-utils/internal\";\nimport { NonRetryableError } from \"@fluidframework/driver-utils/internal\";\nimport { OdspErrorTypes } from \"@fluidframework/odsp-driver-definitions/internal\";\n\nimport { pkgVersion as driverVersion } from \"../packageVersion.js\";\n\nimport {\n\tcreateOdspFileVersionFetcher,\n\ttype OdspFileVersionFetcherProps,\n\ttype OdspFileVersionRef,\n\ttype IOdspFileVersionFetcher,\n} from \"./odspFileVersionFetcher.js\";\n\n// Re-exported so consumers (and this module's own index) can keep importing these fetcher-owned\n// types from the version manager. The definitions live in odspFileVersionFetcher.ts so that file\n// does not depend on this one, avoiding a circular dependency between the two modules.\nexport type { OdspFileVersionRef, IOdspFileVersionFetcher } from \"./odspFileVersionFetcher.js\";\n\n/**\n * An ODSP file version together with its resolved Fluid sequence number.\n */\nexport interface ResolvedVersion extends OdspFileVersionRef {\n\t/**\n\t * The Fluid sequence number the version's snapshot represents.\n\t */\n\treadonly sequenceNumber: number;\n}\n\n/**\n * Result of resolving the base version for a target sequence number.\n *\n * @remarks\n * The tip (newest) version is excluded from base selection, so when the target is at or after the head\n * the base is the newest *sealed* version with `seq <= target` (a normal `found`); if the file's only\n * version is the tip, the result is `noBaseVersion`. The wired consumer surfaces `noBaseVersion` as a\n * `UsageError`; loading the live file for a near-head target is a possible future consumer choice, not\n * current behavior.\n */\nexport type BaseForSeq =\n\t| {\n\t\t\t/** A recoverable version with `sequenceNumber <= target` was found. */\n\t\t\treadonly kind: \"found\";\n\t\t\treadonly base: ResolvedVersion;\n\t }\n\t| {\n\t\t\t/**\n\t\t\t * No sealed version has `sequenceNumber <= target` — the target predates retained history, or\n\t\t\t * the only version is the excluded tip.\n\t\t\t */\n\t\t\treadonly kind: \"noBaseVersion\";\n\t\t\t/** The oldest sequence number that was resolved while searching, if any. */\n\t\t\treadonly oldestResolvedSeq?: number;\n\t };\n\n/**\n * Selects the file version to use as the base for loading or replaying to a target sequence number.\n */\nexport interface IOdspVersionManager {\n\t/**\n\t * Given a target sequence number, return the closest version at or before it (`found`), or\n\t * `noBaseVersion` if the target predates the oldest retained version.\n\t *\n\t * @remarks\n\t * A `found` base is guaranteed to share the live document's ODSP epoch (lineage): before returning\n\t * it, the chosen base's epoch is compared with the live document's, and a mismatch throws a non-retryable error\n\t * rather than returning a base that cannot be replayed. Op availability is enforced separately and\n\t * lazily as the loader reads the bridging ops.\n\t */\n\tfindBaseForSeq(target: number): Promise<BaseForSeq>;\n}\n\n/**\n * Default {@link IOdspVersionManager}. Caches resolved sequence numbers (which never change); the version\n * list is re-enumerated on each query rather than cached, since new versions are cut over time. The\n * resolution strategy (eager, newest-to-oldest, stopping at the first usable base) is hidden behind\n * {@link findBaseForSeq} and can change without affecting callers.\n */\n// Exported only so the same-package tests can construct it with a fake IOdspFileVersionFetcher.\n// Deliberately kept out of the folder barrel and the package public index, so it is not public API.\nexport class OdspVersionManager implements IOdspVersionManager {\n\t// Sealed versions' sequence numbers, memoized so each is resolved at most once per manager instance\n\t// (a sealed version's number is fixed once the version exists).\n\tprivate readonly seqCache = new PromiseCache<string, number>();\n\t// Sealed versions' ODSP epochs, memoized like their sequence numbers. The live document's epoch is NOT\n\t// cached — it can change (restore/reupload), so validateLineageEpoch always reads it fresh.\n\tprivate readonly epochCache = new PromiseCache<string, string | undefined>();\n\n\tpublic constructor(private readonly fetcher: IOdspFileVersionFetcher) {}\n\n\tpublic async findBaseForSeq(target: number): Promise<BaseForSeq> {\n\t\t// Re-enumerate the list each call (it changes as new versions are cut).\n\t\tconst versions = await this.fetcher.listFileVersions();\n\n\t\t// Start past the tip (index 0): the newest version's sequence number can still advance until a newer\n\t\t// version is cut, so it is treated as the live head rather than a stable base. Scan the remaining\n\t\t// (sealed) versions newest-first and return the first with sequence number <= target — the closest\n\t\t// base — or noBaseVersion, reporting the oldest sequence number seen.\n\t\tconst candidates = versions.slice(1);\n\n\t\tlet oldestResolvedSeq: number | undefined;\n\t\tfor (const version of candidates) {\n\t\t\tconst sequenceNumber = await this.resolveSeq(version.versionId);\n\t\t\toldestResolvedSeq =\n\t\t\t\toldestResolvedSeq === undefined\n\t\t\t\t\t? sequenceNumber\n\t\t\t\t\t: Math.min(oldestResolvedSeq, sequenceNumber);\n\t\t\tif (sequenceNumber <= target) {\n\t\t\t\tconst base = { ...version, sequenceNumber };\n\t\t\t\t// Confirm the chosen base shares the live document's lineage before handing it back\n\t\t\t\tawait this.validateLineageEpoch(base);\n\t\t\t\treturn { kind: \"found\", base };\n\t\t\t}\n\t\t}\n\t\treturn { kind: \"noBaseVersion\", oldestResolvedSeq };\n\t}\n\n\tprivate async validateLineageEpoch(base: ResolvedVersion): Promise<void> {\n\t\t// The live document's epoch can change (a restore or download-and-reupload bumps it), so it is\n\t\t// always read fresh. A numbered version's snapshot is immutable, so its epoch never changes and\n\t\t// is cached per versionId (see resolveVersionEpoch).\n\t\tconst [liveEpoch, baseEpoch] = await Promise.all([\n\t\t\tthis.fetcher.getLiveDocumentEpoch(),\n\t\t\tthis.resolveVersionEpoch(base.versionId),\n\t\t]);\n\t\tif (liveEpoch === undefined || baseEpoch === undefined) {\n\t\t\tthrow new NonRetryableError(\n\t\t\t\t`Cannot verify that ODSP file version ${base.versionId} shares the live document's ` +\n\t\t\t\t\t`lineage: the storage response is missing an epoch (base epoch: ${baseEpoch ?? \"unknown\"}, ` +\n\t\t\t\t\t`live epoch: ${liveEpoch ?? \"unknown\"}).`,\n\t\t\t\tOdspErrorTypes.incorrectServerResponse,\n\t\t\t\t{\n\t\t\t\t\tdriverVersion,\n\t\t\t\t\tserverEpoch: liveEpoch,\n\t\t\t\t\tclientEpoch: baseEpoch,\n\t\t\t\t},\n\t\t\t);\n\t\t}\n\t\tif (liveEpoch !== baseEpoch) {\n\t\t\tthrow new NonRetryableError(\n\t\t\t\t`ODSP file version ${base.versionId} is on epoch \"${baseEpoch}\" but the live document is ` +\n\t\t\t\t\t`on epoch \"${liveEpoch}\". A binary file change (e.g. a version restore or ` +\n\t\t\t\t\t`download-and-reupload) renumbered the op stream, so ops cannot be replayed from this ` +\n\t\t\t\t\t`base onto the live document.`,\n\t\t\t\tOdspErrorTypes.fileOverwrittenInStorage,\n\t\t\t\t{\n\t\t\t\t\tdriverVersion,\n\t\t\t\t\tserverEpoch: liveEpoch,\n\t\t\t\t\tclientEpoch: baseEpoch,\n\t\t\t\t},\n\t\t\t);\n\t\t}\n\t}\n\n\tpublic async listVersions(): Promise<ResolvedVersion[]> {\n\t\tconst versions = await this.fetcher.listFileVersions();\n\t\t// Resolution order does not matter, so resolve concurrently; the newest-first array order is\n\t\t// preserved by Promise.all regardless of completion order.\n\t\treturn Promise.all(\n\t\t\tversions.map(async (version, index) => ({\n\t\t\t\t...version,\n\t\t\t\t// Resolve the tip (index 0) fresh each call, since its sequence number can still change;\n\t\t\t\t// sealed versions come from the cache.\n\t\t\t\tsequenceNumber:\n\t\t\t\t\tindex === 0\n\t\t\t\t\t\t? await this.fetcher.resolveSequenceNumber(version.versionId)\n\t\t\t\t\t\t: await this.resolveSeq(version.versionId),\n\t\t\t})),\n\t\t);\n\t}\n\n\tprivate async resolveSeq(versionId: string): Promise<number> {\n\t\t// Cached indefinitely (a sealed version's number is fixed); concurrent calls coalesce and a failed\n\t\t// resolution is evicted so a later call retries.\n\t\treturn this.seqCache.addOrGet(versionId, async () =>\n\t\t\tthis.fetcher.resolveSequenceNumber(versionId),\n\t\t);\n\t}\n\n\tprivate async resolveVersionEpoch(versionId: string): Promise<string | undefined> {\n\t\t// Cached like resolveSeq (a sealed version's epoch is fixed). The live document's epoch is read\n\t\t// fresh instead (see validateLineageEpoch).\n\t\treturn this.epochCache.addOrGet(versionId, async () =>\n\t\t\tthis.fetcher.getRecoverableVersionEpoch(versionId),\n\t\t);\n\t}\n}\n\n/**\n * Create an {@link IOdspVersionManager} for a specific ODSP file, wired to the real ODSP REST APIs.\n */\nexport function createOdspVersionManager(\n\tprops: OdspFileVersionFetcherProps,\n): IOdspVersionManager {\n\treturn new OdspVersionManager(createOdspFileVersionFetcher(props));\n}\n"]}
1
+ {"version":3,"file":"odspVersionManager.js","sourceRoot":"","sources":["../../src/odspVersionManager/odspVersionManager.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH;;;;;;GAMG;AAEH,OAAO,EAAE,YAAY,EAAE,MAAM,qCAAqC,CAAC;AACnE,OAAO,EAAE,iBAAiB,EAAE,MAAM,uCAAuC,CAAC;AAC1E,OAAO,EAAE,cAAc,EAAE,MAAM,kDAAkD,CAAC;AAElF,OAAO,EAAE,UAAU,IAAI,aAAa,EAAE,MAAM,sBAAsB,CAAC;AAEnE,OAAO,EACN,4BAA4B,GAI5B,MAAM,6BAA6B,CAAC;AAoErC;;;;;GAKG;AACH,gGAAgG;AAChG,oGAAoG;AACpG,MAAM,OAAO,kBAAkB;IAQM;IAPpC,oGAAoG;IACpG,gEAAgE;IAC/C,QAAQ,GAAG,IAAI,YAAY,EAAkB,CAAC;IAC/D,uGAAuG;IACvG,4FAA4F;IAC3E,UAAU,GAAG,IAAI,YAAY,EAA8B,CAAC;IAE7E,YAAoC,OAAgC;QAAhC,YAAO,GAAP,OAAO,CAAyB;IAAG,CAAC;IAEjE,KAAK,CAAC,cAAc,CAAC,MAAc;QACzC,wEAAwE;QACxE,MAAM,QAAQ,GAAG,MAAM,IAAI,CAAC,OAAO,CAAC,gBAAgB,EAAE,CAAC;QAEvD,qGAAqG;QACrG,kGAAkG;QAClG,mGAAmG;QACnG,sEAAsE;QACtE,MAAM,UAAU,GAAG,QAAQ,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC;QAErC,IAAI,iBAAqC,CAAC;QAC1C,IAAI,cAAc,GAAG,CAAC,CAAC;QACvB,IAAI,wBAAwB,GAAG,CAAC,CAAC;QACjC,KAAK,MAAM,OAAO,IAAI,UAAU,EAAE,CAAC;YAClC,cAAc,EAAE,CAAC;YACjB,MAAM,cAAc,GAAG,MAAM,IAAI,CAAC,UAAU,CAAC,OAAO,CAAC,SAAS,EAAE,GAAG,EAAE;gBACpE,wBAAwB,EAAE,CAAC;YAC5B,CAAC,CAAC,CAAC;YACH,iBAAiB;gBAChB,iBAAiB,KAAK,SAAS;oBAC9B,CAAC,CAAC,cAAc;oBAChB,CAAC,CAAC,IAAI,CAAC,GAAG,CAAC,iBAAiB,EAAE,cAAc,CAAC,CAAC;YAChD,IAAI,cAAc,IAAI,MAAM,EAAE,CAAC;gBAC9B,MAAM,IAAI,GAAG,EAAE,GAAG,OAAO,EAAE,cAAc,EAAE,CAAC;gBAC5C,oFAAoF;gBACpF,MAAM,IAAI,CAAC,oBAAoB,CAAC,IAAI,CAAC,CAAC;gBACtC,OAAO,EAAE,IAAI,EAAE,OAAO,EAAE,IAAI,EAAE,cAAc,EAAE,wBAAwB,EAAE,CAAC;YAC1E,CAAC;QACF,CAAC;QACD,OAAO;YACN,IAAI,EAAE,eAAe;YACrB,iBAAiB;YACjB,cAAc;YACd,wBAAwB;SACxB,CAAC;IACH,CAAC;IAEO,KAAK,CAAC,oBAAoB,CAAC,IAAqB;QACvD,+FAA+F;QAC/F,gGAAgG;QAChG,qDAAqD;QACrD,MAAM,CAAC,SAAS,EAAE,SAAS,CAAC,GAAG,MAAM,OAAO,CAAC,GAAG,CAAC;YAChD,IAAI,CAAC,OAAO,CAAC,oBAAoB,EAAE;YACnC,IAAI,CAAC,mBAAmB,CAAC,IAAI,CAAC,SAAS,CAAC;SACxC,CAAC,CAAC;QACH,IAAI,SAAS,KAAK,SAAS,IAAI,SAAS,KAAK,SAAS,EAAE,CAAC;YACxD,MAAM,IAAI,iBAAiB,CAC1B,wCAAwC,IAAI,CAAC,SAAS,8BAA8B;gBACnF,kEAAkE,SAAS,IAAI,SAAS,IAAI;gBAC5F,eAAe,SAAS,IAAI,SAAS,IAAI,EAC1C,cAAc,CAAC,uBAAuB,EACtC;gBACC,aAAa;gBACb,WAAW,EAAE,SAAS;gBACtB,WAAW,EAAE,SAAS;aACtB,CACD,CAAC;QACH,CAAC;QACD,IAAI,SAAS,KAAK,SAAS,EAAE,CAAC;YAC7B,MAAM,IAAI,iBAAiB,CAC1B,qBAAqB,IAAI,CAAC,SAAS,iBAAiB,SAAS,6BAA6B;gBACzF,aAAa,SAAS,qDAAqD;gBAC3E,uFAAuF;gBACvF,8BAA8B,EAC/B,cAAc,CAAC,wBAAwB,EACvC;gBACC,aAAa;gBACb,WAAW,EAAE,SAAS;gBACtB,WAAW,EAAE,SAAS;gBACtB,8BAA8B,EAAE,iBAAiB;aACjD,CACD,CAAC;QACH,CAAC;IACF,CAAC;IAEM,KAAK,CAAC,YAAY;QACxB,MAAM,QAAQ,GAAG,MAAM,IAAI,CAAC,OAAO,CAAC,gBAAgB,EAAE,CAAC;QACvD,6FAA6F;QAC7F,2DAA2D;QAC3D,OAAO,OAAO,CAAC,GAAG,CACjB,QAAQ,CAAC,GAAG,CAAC,KAAK,EAAE,OAAO,EAAE,KAAK,EAAE,EAAE,CAAC,CAAC;YACvC,GAAG,OAAO;YACV,yFAAyF;YACzF,uCAAuC;YACvC,cAAc,EACb,KAAK,KAAK,CAAC;gBACV,CAAC,CAAC,MAAM,IAAI,CAAC,OAAO,CAAC,qBAAqB,CAAC,OAAO,CAAC,SAAS,CAAC;gBAC7D,CAAC,CAAC,MAAM,IAAI,CAAC,UAAU,CAAC,OAAO,CAAC,SAAS,CAAC;SAC5C,CAAC,CAAC,CACH,CAAC;IACH,CAAC;IAEO,KAAK,CAAC,UAAU,CAAC,SAAiB,EAAE,WAAwB;QACnE,mGAAmG;QACnG,iDAAiD;QACjD,OAAO,IAAI,CAAC,QAAQ,CAAC,QAAQ,CAAC,SAAS,EAAE,KAAK,IAAI,EAAE;YACnD,WAAW,EAAE,EAAE,CAAC;YAChB,OAAO,IAAI,CAAC,OAAO,CAAC,qBAAqB,CAAC,SAAS,CAAC,CAAC;QACtD,CAAC,CAAC,CAAC;IACJ,CAAC;IAEO,KAAK,CAAC,mBAAmB,CAAC,SAAiB;QAClD,gGAAgG;QAChG,4CAA4C;QAC5C,OAAO,IAAI,CAAC,UAAU,CAAC,QAAQ,CAAC,SAAS,EAAE,KAAK,IAAI,EAAE,CACrD,IAAI,CAAC,OAAO,CAAC,0BAA0B,CAAC,SAAS,CAAC,CAClD,CAAC;IACH,CAAC;CACD;AAED;;GAEG;AACH,MAAM,UAAU,wBAAwB,CACvC,KAAkC;IAElC,OAAO,IAAI,kBAAkB,CAAC,4BAA4B,CAAC,KAAK,CAAC,CAAC,CAAC;AACpE,CAAC","sourcesContent":["/*!\n * Copyright (c) Microsoft Corporation and contributors. All rights reserved.\n * Licensed under the MIT License.\n */\n\n/**\n * Selects the ODSP file version whose snapshot sits at or before a target Fluid sequence number —\n * the base to load or replay from when materializing a document at a point in time.\n *\n * The selection logic depends on an injected {@link IOdspFileVersionFetcher}, so it is independent of\n * how versions are enumerated and resolved (real ODSP, a test double, or an alternative backend).\n */\n\nimport { PromiseCache } from \"@fluidframework/core-utils/internal\";\nimport { NonRetryableError } from \"@fluidframework/driver-utils/internal\";\nimport { OdspErrorTypes } from \"@fluidframework/odsp-driver-definitions/internal\";\n\nimport { pkgVersion as driverVersion } from \"../packageVersion.js\";\n\nimport {\n\tcreateOdspFileVersionFetcher,\n\ttype OdspFileVersionFetcherProps,\n\ttype OdspFileVersionRef,\n\ttype IOdspFileVersionFetcher,\n} from \"./odspFileVersionFetcher.js\";\n\n// Re-exported so consumers (and this module's own index) can keep importing these fetcher-owned\n// types from the version manager. The definitions live in odspFileVersionFetcher.ts so that file\n// does not depend on this one, avoiding a circular dependency between the two modules.\nexport type { OdspFileVersionRef, IOdspFileVersionFetcher } from \"./odspFileVersionFetcher.js\";\n\n/**\n * An ODSP file version together with its resolved Fluid sequence number.\n */\nexport interface ResolvedVersion extends OdspFileVersionRef {\n\t/**\n\t * The Fluid sequence number the version's snapshot represents.\n\t */\n\treadonly sequenceNumber: number;\n}\n\n/**\n * Result of resolving the base version for a target sequence number.\n *\n * @remarks\n * The tip (newest) version is excluded from base selection, so when the target is at or after the head\n * the base is the newest *sealed* version with `seq <= target` (a normal `found`); if the file's only\n * version is the tip, the result is `noBaseVersion`. The wired consumer surfaces `noBaseVersion` as a\n * `UsageError`; loading the live file for a near-head target is a possible future consumer choice, not\n * current behavior.\n */\nexport type BaseForSeq =\n\t| {\n\t\t\t/** A recoverable version with `sequenceNumber <= target` was found. */\n\t\t\treadonly kind: \"found\";\n\t\t\treadonly base: ResolvedVersion;\n\t\t\t/** Number of sealed version candidates examined before selecting the base. */\n\t\t\treadonly versionsProbed: number;\n\t\t\t/** Number of candidate sequence numbers that required an uncached ODSP fetch. */\n\t\t\treadonly sequenceNumberFetchCount: number;\n\t }\n\t| {\n\t\t\t/**\n\t\t\t * No sealed version has `sequenceNumber <= target` — the target predates retained history, or\n\t\t\t * the only version is the excluded tip.\n\t\t\t */\n\t\t\treadonly kind: \"noBaseVersion\";\n\t\t\t/** The oldest sequence number that was resolved while searching, if any. */\n\t\t\treadonly oldestResolvedSeq?: number;\n\t\t\t/** Number of sealed version candidates examined before exhausting retained history. */\n\t\t\treadonly versionsProbed: number;\n\t\t\t/** Number of candidate sequence numbers that required an uncached ODSP fetch. */\n\t\t\treadonly sequenceNumberFetchCount: number;\n\t };\n\n/**\n * Selects the file version to use as the base for loading or replaying to a target sequence number.\n */\nexport interface IOdspVersionManager {\n\t/**\n\t * Given a target sequence number, return the closest version at or before it (`found`), or\n\t * `noBaseVersion` if the target predates the oldest retained version.\n\t *\n\t * @remarks\n\t * A `found` base is guaranteed to share the live document's ODSP epoch (lineage): before returning\n\t * it, the chosen base's epoch is compared with the live document's, and a mismatch throws a non-retryable error\n\t * rather than returning a base that cannot be replayed. Op availability is enforced separately and\n\t * lazily as the loader reads the bridging ops.\n\t */\n\tfindBaseForSeq(target: number): Promise<BaseForSeq>;\n}\n\n/**\n * Default {@link IOdspVersionManager}. Caches resolved sequence numbers (which never change); the version\n * list is re-enumerated on each query rather than cached, since new versions are cut over time. The\n * resolution strategy (eager, newest-to-oldest, stopping at the first usable base) is hidden behind\n * {@link findBaseForSeq} and can change without affecting callers.\n */\n// Exported only so the same-package tests can construct it with a fake IOdspFileVersionFetcher.\n// Deliberately kept out of the folder barrel and the package public index, so it is not public API.\nexport class OdspVersionManager implements IOdspVersionManager {\n\t// Sealed versions' sequence numbers, memoized so each is resolved at most once per manager instance\n\t// (a sealed version's number is fixed once the version exists).\n\tprivate readonly seqCache = new PromiseCache<string, number>();\n\t// Sealed versions' ODSP epochs, memoized like their sequence numbers. The live document's epoch is NOT\n\t// cached — it can change (restore/reupload), so validateLineageEpoch always reads it fresh.\n\tprivate readonly epochCache = new PromiseCache<string, string | undefined>();\n\n\tpublic constructor(private readonly fetcher: IOdspFileVersionFetcher) {}\n\n\tpublic async findBaseForSeq(target: number): Promise<BaseForSeq> {\n\t\t// Re-enumerate the list each call (it changes as new versions are cut).\n\t\tconst versions = await this.fetcher.listFileVersions();\n\n\t\t// Start past the tip (index 0): the newest version's sequence number can still advance until a newer\n\t\t// version is cut, so it is treated as the live head rather than a stable base. Scan the remaining\n\t\t// (sealed) versions newest-first and return the first with sequence number <= target — the closest\n\t\t// base — or noBaseVersion, reporting the oldest sequence number seen.\n\t\tconst candidates = versions.slice(1);\n\n\t\tlet oldestResolvedSeq: number | undefined;\n\t\tlet versionsProbed = 0;\n\t\tlet sequenceNumberFetchCount = 0;\n\t\tfor (const version of candidates) {\n\t\t\tversionsProbed++;\n\t\t\tconst sequenceNumber = await this.resolveSeq(version.versionId, () => {\n\t\t\t\tsequenceNumberFetchCount++;\n\t\t\t});\n\t\t\toldestResolvedSeq =\n\t\t\t\toldestResolvedSeq === undefined\n\t\t\t\t\t? sequenceNumber\n\t\t\t\t\t: Math.min(oldestResolvedSeq, sequenceNumber);\n\t\t\tif (sequenceNumber <= target) {\n\t\t\t\tconst base = { ...version, sequenceNumber };\n\t\t\t\t// Confirm the chosen base shares the live document's lineage before handing it back\n\t\t\t\tawait this.validateLineageEpoch(base);\n\t\t\t\treturn { kind: \"found\", base, versionsProbed, sequenceNumberFetchCount };\n\t\t\t}\n\t\t}\n\t\treturn {\n\t\t\tkind: \"noBaseVersion\",\n\t\t\toldestResolvedSeq,\n\t\t\tversionsProbed,\n\t\t\tsequenceNumberFetchCount,\n\t\t};\n\t}\n\n\tprivate async validateLineageEpoch(base: ResolvedVersion): Promise<void> {\n\t\t// The live document's epoch can change (a restore or download-and-reupload bumps it), so it is\n\t\t// always read fresh. A numbered version's snapshot is immutable, so its epoch never changes and\n\t\t// is cached per versionId (see resolveVersionEpoch).\n\t\tconst [liveEpoch, baseEpoch] = await Promise.all([\n\t\t\tthis.fetcher.getLiveDocumentEpoch(),\n\t\t\tthis.resolveVersionEpoch(base.versionId),\n\t\t]);\n\t\tif (liveEpoch === undefined || baseEpoch === undefined) {\n\t\t\tthrow new NonRetryableError(\n\t\t\t\t`Cannot verify that ODSP file version ${base.versionId} shares the live document's ` +\n\t\t\t\t\t`lineage: the storage response is missing an epoch (base epoch: ${baseEpoch ?? \"unknown\"}, ` +\n\t\t\t\t\t`live epoch: ${liveEpoch ?? \"unknown\"}).`,\n\t\t\t\tOdspErrorTypes.incorrectServerResponse,\n\t\t\t\t{\n\t\t\t\t\tdriverVersion,\n\t\t\t\t\tserverEpoch: liveEpoch,\n\t\t\t\t\tclientEpoch: baseEpoch,\n\t\t\t\t},\n\t\t\t);\n\t\t}\n\t\tif (liveEpoch !== baseEpoch) {\n\t\t\tthrow new NonRetryableError(\n\t\t\t\t`ODSP file version ${base.versionId} is on epoch \"${baseEpoch}\" but the live document is ` +\n\t\t\t\t\t`on epoch \"${liveEpoch}\". A binary file change (e.g. a version restore or ` +\n\t\t\t\t\t`download-and-reupload) renumbered the op stream, so ops cannot be replayed from this ` +\n\t\t\t\t\t`base onto the live document.`,\n\t\t\t\tOdspErrorTypes.fileOverwrittenInStorage,\n\t\t\t\t{\n\t\t\t\t\tdriverVersion,\n\t\t\t\t\tserverEpoch: liveEpoch,\n\t\t\t\t\tclientEpoch: baseEpoch,\n\t\t\t\t\tversionMarkAvailabilityOutcome: \"lineageMismatch\",\n\t\t\t\t},\n\t\t\t);\n\t\t}\n\t}\n\n\tpublic async listVersions(): Promise<ResolvedVersion[]> {\n\t\tconst versions = await this.fetcher.listFileVersions();\n\t\t// Resolution order does not matter, so resolve concurrently; the newest-first array order is\n\t\t// preserved by Promise.all regardless of completion order.\n\t\treturn Promise.all(\n\t\t\tversions.map(async (version, index) => ({\n\t\t\t\t...version,\n\t\t\t\t// Resolve the tip (index 0) fresh each call, since its sequence number can still change;\n\t\t\t\t// sealed versions come from the cache.\n\t\t\t\tsequenceNumber:\n\t\t\t\t\tindex === 0\n\t\t\t\t\t\t? await this.fetcher.resolveSequenceNumber(version.versionId)\n\t\t\t\t\t\t: await this.resolveSeq(version.versionId),\n\t\t\t})),\n\t\t);\n\t}\n\n\tprivate async resolveSeq(versionId: string, onCacheMiss?: () => void): Promise<number> {\n\t\t// Cached indefinitely (a sealed version's number is fixed); concurrent calls coalesce and a failed\n\t\t// resolution is evicted so a later call retries.\n\t\treturn this.seqCache.addOrGet(versionId, async () => {\n\t\t\tonCacheMiss?.();\n\t\t\treturn this.fetcher.resolveSequenceNumber(versionId);\n\t\t});\n\t}\n\n\tprivate async resolveVersionEpoch(versionId: string): Promise<string | undefined> {\n\t\t// Cached like resolveSeq (a sealed version's epoch is fixed). The live document's epoch is read\n\t\t// fresh instead (see validateLineageEpoch).\n\t\treturn this.epochCache.addOrGet(versionId, async () =>\n\t\t\tthis.fetcher.getRecoverableVersionEpoch(versionId),\n\t\t);\n\t}\n}\n\n/**\n * Create an {@link IOdspVersionManager} for a specific ODSP file, wired to the real ODSP REST APIs.\n */\nexport function createOdspVersionManager(\n\tprops: OdspFileVersionFetcherProps,\n): IOdspVersionManager {\n\treturn new OdspVersionManager(createOdspFileVersionFetcher(props));\n}\n"]}
@@ -5,5 +5,5 @@
5
5
  * THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY
6
6
  */
7
7
  export declare const pkgName = "@fluidframework/odsp-driver";
8
- export declare const pkgVersion = "3.2.0";
8
+ export declare const pkgVersion = "3.3.0";
9
9
  //# sourceMappingURL=packageVersion.d.ts.map
@@ -5,5 +5,5 @@
5
5
  * THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY
6
6
  */
7
7
  export const pkgName = "@fluidframework/odsp-driver";
8
- export const pkgVersion = "3.2.0";
8
+ export const pkgVersion = "3.3.0";
9
9
  //# sourceMappingURL=packageVersion.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"packageVersion.js","sourceRoot":"","sources":["../src/packageVersion.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,MAAM,CAAC,MAAM,OAAO,GAAG,6BAA6B,CAAC;AACrD,MAAM,CAAC,MAAM,UAAU,GAAG,OAAO,CAAC","sourcesContent":["/*!\n * Copyright (c) Microsoft Corporation and contributors. All rights reserved.\n * Licensed under the MIT License.\n *\n * THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY\n */\n\nexport const pkgName = \"@fluidframework/odsp-driver\";\nexport const pkgVersion = \"3.2.0\";\n"]}
1
+ {"version":3,"file":"packageVersion.js","sourceRoot":"","sources":["../src/packageVersion.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,MAAM,CAAC,MAAM,OAAO,GAAG,6BAA6B,CAAC;AACrD,MAAM,CAAC,MAAM,UAAU,GAAG,OAAO,CAAC","sourcesContent":["/*!\n * Copyright (c) Microsoft Corporation and contributors. All rights reserved.\n * Licensed under the MIT License.\n *\n * THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY\n */\n\nexport const pkgName = \"@fluidframework/odsp-driver\";\nexport const pkgVersion = \"3.3.0\";\n"]}
@@ -1 +1 @@
1
- {"version":3,"file":"createPointInTimeDocumentService.d.ts","sourceRoot":"","sources":["../../src/pointInTimeDriver/createPointInTimeDocumentService.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,OAAO,KAAK,EACX,gBAAgB,EAChB,YAAY,EACZ,MAAM,6CAA6C,CAAC;AAErD,OAAO,KAAK,EACX,gBAAgB,EAEhB,MAAM,kDAAkD,CAAC;AAC1D,OAAO,EAEN,KAAK,kBAAkB,EACvB,MAAM,0CAA0C,CAAC;AAElD,OAAO,EAA6B,KAAK,YAAY,EAAE,MAAM,oBAAoB,CAAC;AAGlF,OAAO,KAAK,EAAE,kDAAkD,EAAE,MAAM,sCAAsC,CAAC;AAM/G,OAAO,EAEN,KAAK,mBAAmB,EAExB,MAAM,6CAA6C,CAAC;AAErD,OAAO,EAAE,8BAA8B,EAAE,MAAM,qCAAqC,CAAC;AAErF,UAAU,uCAAuC;IAChD,QAAQ,CAAC,oBAAoB,CAAC,EAAE,CAC/B,eAAe,EAAE,gBAAgB,EACjC,MAAM,EAAE,kBAAkB,EAC1B,YAAY,EAAE,YAAY,KACtB,mBAAmB,GAAG,OAAO,CAAC,mBAAmB,CAAC,CAAC;IACxD,QAAQ,CAAC,kBAAkB,CAAC,EAAE,CAC7B,WAAW,EAAE,YAAY,EACzB,WAAW,EAAE,MAAM,KACf,YAAY,GAAG,OAAO,CAAC,YAAY,CAAC,CAAC;CAC1C;AAiDD;;;;;;;;;;GAUG;AACH,wBAAsB,gCAAgC,CACrD,KAAK,EAAE,kDAAkD,GACvD,OAAO,CAAC,gBAAgB,CAAC,CAE3B;AAED;;;GAGG;AACH,wBAAsB,oCAAoC,CACzD,EACC,WAAW,EACX,oBAAoB,EACpB,MAAM,EACN,kBAAkB,EAClB,cAAc,EACd,eAAe,EACf,cAAc,EACd,qBAAqB,GACrB,EAAE,kDAAkD,EACrD,YAAY,GAAE,uCAA4C,GACxD,OAAO,CAAC,8BAA8B,CAAC,CAoEzC"}
1
+ {"version":3,"file":"createPointInTimeDocumentService.d.ts","sourceRoot":"","sources":["../../src/pointInTimeDriver/createPointInTimeDocumentService.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAGH,OAAO,KAAK,EACX,gBAAgB,EAChB,YAAY,EACZ,MAAM,6CAA6C,CAAC;AAErD,OAAO,KAAK,EACX,gBAAgB,EAEhB,MAAM,kDAAkD,CAAC;AAC1D,OAAO,EAGN,KAAK,kBAAkB,EACvB,MAAM,0CAA0C,CAAC;AAElD,OAAO,EAA6B,KAAK,YAAY,EAAE,MAAM,oBAAoB,CAAC;AAGlF,OAAO,KAAK,EAAE,kDAAkD,EAAE,MAAM,sCAAsC,CAAC;AAM/G,OAAO,EAEN,KAAK,mBAAmB,EAExB,MAAM,6CAA6C,CAAC;AAErD,OAAO,EAAE,8BAA8B,EAAE,MAAM,qCAAqC,CAAC;AAErF,UAAU,uCAAuC;IAChD,QAAQ,CAAC,oBAAoB,CAAC,EAAE,CAC/B,eAAe,EAAE,gBAAgB,EACjC,MAAM,EAAE,kBAAkB,EAC1B,YAAY,EAAE,YAAY,KACtB,mBAAmB,GAAG,OAAO,CAAC,mBAAmB,CAAC,CAAC;IACxD,QAAQ,CAAC,kBAAkB,CAAC,EAAE,CAC7B,WAAW,EAAE,YAAY,EACzB,WAAW,EAAE,MAAM,KACf,YAAY,GAAG,OAAO,CAAC,YAAY,CAAC,CAAC;CAC1C;AAiDD;;;;;;;;;;GAUG;AACH,wBAAsB,gCAAgC,CACrD,KAAK,EAAE,kDAAkD,GACvD,OAAO,CAAC,gBAAgB,CAAC,CAE3B;AAED;;;GAGG;AACH,wBAAsB,oCAAoC,CACzD,EACC,WAAW,EACX,oBAAoB,EACpB,MAAM,EACN,kBAAkB,EAClB,cAAc,EACd,eAAe,EACf,cAAc,EACd,qBAAqB,GACrB,EAAE,kDAAkD,EACrD,YAAY,GAAE,uCAA4C,GACxD,OAAO,CAAC,8BAA8B,CAAC,CAqHzC"}
@@ -2,8 +2,9 @@
2
2
  * Copyright (c) Microsoft Corporation and contributors. All rights reserved.
3
3
  * Licensed under the MIT License.
4
4
  */
5
+ import { performanceNow } from "@fluid-internal/client-utils";
5
6
  import { UsageError } from "@fluidframework/driver-utils/internal";
6
- import { createChildLogger, } from "@fluidframework/telemetry-utils/internal";
7
+ import { createChildLogger, isILoggingError, } from "@fluidframework/telemetry-utils/internal";
7
8
  import { createOdspCacheAndTracker } from "../epochTracker.js";
8
9
  import { NonPersistentCache } from "../odspCache.js";
9
10
  import { OdspDriverUrlResolver } from "../odspDriverUrlResolver.js";
@@ -71,15 +72,54 @@ export async function createPointInTimeDocumentServiceCore({ resolvedUrl, target
71
72
  docId: odspResolvedUrl.hashedDocumentId,
72
73
  fileVersion: odspResolvedUrl.fileVersion,
73
74
  }, extLogger, clientIsSummarizer, requestHeaders);
74
- const versionManager = await (dependencies.createVersionManager ??
75
- (async (url, versionLogger, epochTracker) => createVersionManager(url, versionLogger, epochTracker, getStorageToken, requestHeaders)))(odspResolvedUrl, extLogger, cacheAndTracker.epochTracker);
76
- const baseResult = await versionManager.findBaseForSeq(targetSequenceNumber);
75
+ const baseSelectionStartTime = performanceNow();
76
+ let baseResult;
77
+ try {
78
+ const versionManager = await (dependencies.createVersionManager ??
79
+ (async (url, versionLogger, epochTracker) => createVersionManager(url, versionLogger, epochTracker, getStorageToken, requestHeaders)))(odspResolvedUrl, extLogger, cacheAndTracker.epochTracker);
80
+ baseResult = await versionManager.findBaseForSeq(targetSequenceNumber);
81
+ }
82
+ catch (error) {
83
+ extLogger.sendErrorEvent({
84
+ eventName: "VersionMarkBaseVersionSelection",
85
+ outcome: "failed",
86
+ duration: performanceNow() - baseSelectionStartTime,
87
+ targetSequenceNumber,
88
+ errorType: error?.errorType,
89
+ availabilityOutcome: isILoggingError(error)
90
+ ? error.getTelemetryProperties().versionMarkAvailabilityOutcome
91
+ : undefined,
92
+ }, error);
93
+ throw error;
94
+ }
77
95
  if (baseResult.kind === "noBaseVersion") {
78
96
  const oldestResolvedSequenceDetail = baseResult.oldestResolvedSeq === undefined
79
97
  ? ""
80
98
  : ` The oldest resolved file version is at sequence number ${baseResult.oldestResolvedSeq}.`;
81
- throw new UsageError(`No ODSP file version is available at or before sequence number ${targetSequenceNumber}.${oldestResolvedSequenceDetail}`);
99
+ const error = new UsageError(`No ODSP file version is available at or before sequence number ${targetSequenceNumber}.${oldestResolvedSequenceDetail}`);
100
+ error.addTelemetryProperties({
101
+ versionMarkAvailabilityOutcome: "baseVersionMissing",
102
+ });
103
+ extLogger.sendErrorEvent({
104
+ eventName: "VersionMarkBaseVersionSelection",
105
+ outcome: "failed",
106
+ duration: performanceNow() - baseSelectionStartTime,
107
+ targetSequenceNumber,
108
+ availabilityOutcome: "baseVersionMissing",
109
+ oldestResolvedSequenceNumber: baseResult.oldestResolvedSeq,
110
+ versionsProbed: baseResult.versionsProbed,
111
+ sequenceNumberFetchCount: baseResult.sequenceNumberFetchCount,
112
+ errorType: error.errorType,
113
+ });
114
+ throw error;
82
115
  }
116
+ extLogger.sendPerformanceEvent({
117
+ eventName: "VersionMarkBaseVersionSelection",
118
+ outcome: "succeeded",
119
+ duration: performanceNow() - baseSelectionStartTime,
120
+ versionsProbed: baseResult.versionsProbed,
121
+ sequenceNumberFetchCount: baseResult.sequenceNumberFetchCount,
122
+ });
83
123
  const recoverableResolvedUrl = await (dependencies.resolveFileVersion ?? resolveFileVersion)(resolvedUrl, baseResult.base.versionId);
84
124
  const recoverableDocumentService = await createDocumentService(recoverableResolvedUrl, odspLogger, cacheAndTracker, clientIsSummarizer);
85
125
  let liveDocumentService;
@@ -91,7 +131,9 @@ export async function createPointInTimeDocumentServiceCore({ resolvedUrl, target
91
131
  recoverableDocumentService.dispose();
92
132
  }
93
133
  catch (disposeError) {
94
- extLogger.sendErrorEvent({ eventName: "PointInTimeRecoverableDocumentServiceDisposeError" }, disposeError);
134
+ extLogger.sendErrorEvent({
135
+ eventName: "VersionMarkRecoverableDocumentServiceDisposeError",
136
+ }, disposeError);
95
137
  }
96
138
  throw error;
97
139
  }
@@ -1 +1 @@
1
- {"version":3,"file":"createPointInTimeDocumentService.js","sourceRoot":"","sources":["../../src/pointInTimeDriver/createPointInTimeDocumentService.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\nimport type {\n\tIDocumentService,\n\tIResolvedUrl,\n} from \"@fluidframework/driver-definitions/internal\";\nimport { UsageError } from \"@fluidframework/driver-utils/internal\";\nimport type {\n\tIOdspResolvedUrl,\n\tIOdspUrlParts,\n} from \"@fluidframework/odsp-driver-definitions/internal\";\nimport {\n\tcreateChildLogger,\n\ttype TelemetryLoggerExt,\n} from \"@fluidframework/telemetry-utils/internal\";\n\nimport { createOdspCacheAndTracker, type EpochTracker } from \"../epochTracker.js\";\nimport { NonPersistentCache } from \"../odspCache.js\";\nimport { OdspDriverUrlResolver } from \"../odspDriverUrlResolver.js\";\nimport type { IOdspPointInTimeDocumentServiceImplementationProps } from \"../odspDocumentServiceFactoryCore.js\";\nimport {\n\tcreateOdspLogger,\n\tgetOdspResolvedUrl,\n\ttoInstrumentedOdspStorageTokenFetcher,\n} from \"../odspUtils.js\";\nimport {\n\tcreateOdspVersionManager,\n\ttype IOdspVersionManager,\n\t// eslint-disable-next-line import-x/no-internal-modules -- the feature implementation owns version selection\n} from \"../odspVersionManager/odspVersionManager.js\";\n\nimport { OdspPointInTimeDocumentService } from \"./odspPointInTimeDocumentService.js\";\n\ninterface IPointInTimeDocumentServiceDependencies {\n\treadonly createVersionManager?: (\n\t\todspResolvedUrl: IOdspResolvedUrl,\n\t\tlogger: TelemetryLoggerExt,\n\t\tepochTracker: EpochTracker,\n\t) => IOdspVersionManager | Promise<IOdspVersionManager>;\n\treadonly resolveFileVersion?: (\n\t\tresolvedUrl: IResolvedUrl,\n\t\tfileVersion: string,\n\t) => IResolvedUrl | Promise<IResolvedUrl>;\n}\n\nasync function createVersionManager(\n\todspResolvedUrl: IOdspResolvedUrl,\n\tlogger: TelemetryLoggerExt,\n\tepochTracker: EpochTracker,\n\tgetStorageToken: IOdspPointInTimeDocumentServiceImplementationProps[\"getStorageToken\"],\n\trequestHeaders?: Readonly<Record<string, string>>,\n): Promise<IOdspVersionManager> {\n\tconst urlParts: IOdspUrlParts = {\n\t\tsiteUrl: odspResolvedUrl.siteUrl,\n\t\tdriveId: odspResolvedUrl.driveId,\n\t\titemId: odspResolvedUrl.itemId,\n\t};\n\tconst getAuthHeader = toInstrumentedOdspStorageTokenFetcher(\n\t\tlogger,\n\t\turlParts,\n\t\tgetStorageToken,\n\t);\n\treturn createOdspVersionManager({\n\t\turlParts,\n\t\tgetAuthHeader,\n\t\tepochTracker,\n\t\tlogger,\n\t\trequestHeaders,\n\t});\n}\n\nasync function resolveFileVersion(\n\tresolvedUrl: IResolvedUrl,\n\tfileVersion: string,\n): Promise<IResolvedUrl> {\n\tconst odspResolvedUrl = getOdspResolvedUrl(resolvedUrl);\n\tconst query = new URLSearchParams({\n\t\tdriveId: odspResolvedUrl.driveId,\n\t\titemId: odspResolvedUrl.itemId,\n\t\tfileVersion,\n\t});\n\tif (odspResolvedUrl.dataStorePath !== undefined) {\n\t\tquery.set(\"path\", odspResolvedUrl.dataStorePath);\n\t}\n\tif (odspResolvedUrl.codeHint?.containerPackageName !== undefined) {\n\t\tquery.set(\"containerPackageName\", odspResolvedUrl.codeHint.containerPackageName);\n\t}\n\treturn new OdspDriverUrlResolver().resolve({\n\t\turl: `${odspResolvedUrl.siteUrl}?${query.toString()}`,\n\t});\n}\n\n/**\n * ODSP's point-in-time document service implementation.\n *\n * @remarks Import this function from the dedicated point-in-time entrypoint and inject it through\n * {@link IOdspDocumentServiceFactoryOptions.pointInTimeDocumentServiceImplementation}.\n *\n * @param props - ODSP service dependencies and point-in-time load parameters supplied by the factory.\n * @returns A read-only ODSP document service materialized at the requested sequence number.\n *\n * @legacy @beta\n */\nexport async function createPointInTimeDocumentService(\n\tprops: IOdspPointInTimeDocumentServiceImplementationProps,\n): Promise<IDocumentService> {\n\treturn createPointInTimeDocumentServiceCore(props);\n}\n\n/**\n * Creates the point-in-time service with replaceable dependencies for testing.\n * @internal\n */\nexport async function createPointInTimeDocumentServiceCore(\n\t{\n\t\tresolvedUrl,\n\t\ttargetSequenceNumber,\n\t\tlogger,\n\t\tclientIsSummarizer,\n\t\tpersistedCache,\n\t\tgetStorageToken,\n\t\trequestHeaders,\n\t\tcreateDocumentService,\n\t}: IOdspPointInTimeDocumentServiceImplementationProps,\n\tdependencies: IPointInTimeDocumentServiceDependencies = {},\n): Promise<OdspPointInTimeDocumentService> {\n\tconst odspLogger = createOdspLogger(logger);\n\tconst extLogger = createChildLogger({ logger: odspLogger });\n\tconst odspResolvedUrl = getOdspResolvedUrl(resolvedUrl);\n\tconst cacheAndTracker = createOdspCacheAndTracker(\n\t\tpersistedCache,\n\t\tnew NonPersistentCache(),\n\t\t{\n\t\t\tresolvedUrl: odspResolvedUrl,\n\t\t\tdocId: odspResolvedUrl.hashedDocumentId,\n\t\t\tfileVersion: odspResolvedUrl.fileVersion,\n\t\t},\n\t\textLogger,\n\t\tclientIsSummarizer,\n\t\trequestHeaders,\n\t);\n\n\tconst versionManager = await (\n\t\tdependencies.createVersionManager ??\n\t\t(async (url, versionLogger, epochTracker) =>\n\t\t\tcreateVersionManager(url, versionLogger, epochTracker, getStorageToken, requestHeaders))\n\t)(odspResolvedUrl, extLogger, cacheAndTracker.epochTracker);\n\tconst baseResult = await versionManager.findBaseForSeq(targetSequenceNumber);\n\tif (baseResult.kind === \"noBaseVersion\") {\n\t\tconst oldestResolvedSequenceDetail =\n\t\t\tbaseResult.oldestResolvedSeq === undefined\n\t\t\t\t? \"\"\n\t\t\t\t: ` The oldest resolved file version is at sequence number ${baseResult.oldestResolvedSeq}.`;\n\t\tthrow new UsageError(\n\t\t\t`No ODSP file version is available at or before sequence number ${targetSequenceNumber}.${oldestResolvedSequenceDetail}`,\n\t\t);\n\t}\n\n\tconst recoverableResolvedUrl = await (dependencies.resolveFileVersion ?? resolveFileVersion)(\n\t\tresolvedUrl,\n\t\tbaseResult.base.versionId,\n\t);\n\tconst recoverableDocumentService = await createDocumentService(\n\t\trecoverableResolvedUrl,\n\t\todspLogger,\n\t\tcacheAndTracker,\n\t\tclientIsSummarizer,\n\t);\n\tlet liveDocumentService: IDocumentService;\n\ttry {\n\t\tliveDocumentService = await createDocumentService(\n\t\t\tresolvedUrl,\n\t\t\todspLogger,\n\t\t\tcacheAndTracker,\n\t\t\tclientIsSummarizer,\n\t\t);\n\t} catch (error) {\n\t\ttry {\n\t\t\trecoverableDocumentService.dispose();\n\t\t} catch (disposeError) {\n\t\t\textLogger.sendErrorEvent(\n\t\t\t\t{ eventName: \"PointInTimeRecoverableDocumentServiceDisposeError\" },\n\t\t\t\tdisposeError,\n\t\t\t);\n\t\t}\n\t\tthrow error;\n\t}\n\treturn new OdspPointInTimeDocumentService(\n\t\trecoverableResolvedUrl,\n\t\trecoverableDocumentService,\n\t\tliveDocumentService,\n\t\ttargetSequenceNumber,\n\t);\n}\n"]}
1
+ {"version":3,"file":"createPointInTimeDocumentService.js","sourceRoot":"","sources":["../../src/pointInTimeDriver/createPointInTimeDocumentService.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\nimport { performanceNow } from \"@fluid-internal/client-utils\";\nimport type {\n\tIDocumentService,\n\tIResolvedUrl,\n} from \"@fluidframework/driver-definitions/internal\";\nimport { UsageError } from \"@fluidframework/driver-utils/internal\";\nimport type {\n\tIOdspResolvedUrl,\n\tIOdspUrlParts,\n} from \"@fluidframework/odsp-driver-definitions/internal\";\nimport {\n\tcreateChildLogger,\n\tisILoggingError,\n\ttype TelemetryLoggerExt,\n} from \"@fluidframework/telemetry-utils/internal\";\n\nimport { createOdspCacheAndTracker, type EpochTracker } from \"../epochTracker.js\";\nimport { NonPersistentCache } from \"../odspCache.js\";\nimport { OdspDriverUrlResolver } from \"../odspDriverUrlResolver.js\";\nimport type { IOdspPointInTimeDocumentServiceImplementationProps } from \"../odspDocumentServiceFactoryCore.js\";\nimport {\n\tcreateOdspLogger,\n\tgetOdspResolvedUrl,\n\ttoInstrumentedOdspStorageTokenFetcher,\n} from \"../odspUtils.js\";\nimport {\n\tcreateOdspVersionManager,\n\ttype IOdspVersionManager,\n\t// eslint-disable-next-line import-x/no-internal-modules -- the feature implementation owns version selection\n} from \"../odspVersionManager/odspVersionManager.js\";\n\nimport { OdspPointInTimeDocumentService } from \"./odspPointInTimeDocumentService.js\";\n\ninterface IPointInTimeDocumentServiceDependencies {\n\treadonly createVersionManager?: (\n\t\todspResolvedUrl: IOdspResolvedUrl,\n\t\tlogger: TelemetryLoggerExt,\n\t\tepochTracker: EpochTracker,\n\t) => IOdspVersionManager | Promise<IOdspVersionManager>;\n\treadonly resolveFileVersion?: (\n\t\tresolvedUrl: IResolvedUrl,\n\t\tfileVersion: string,\n\t) => IResolvedUrl | Promise<IResolvedUrl>;\n}\n\nasync function createVersionManager(\n\todspResolvedUrl: IOdspResolvedUrl,\n\tlogger: TelemetryLoggerExt,\n\tepochTracker: EpochTracker,\n\tgetStorageToken: IOdspPointInTimeDocumentServiceImplementationProps[\"getStorageToken\"],\n\trequestHeaders?: Readonly<Record<string, string>>,\n): Promise<IOdspVersionManager> {\n\tconst urlParts: IOdspUrlParts = {\n\t\tsiteUrl: odspResolvedUrl.siteUrl,\n\t\tdriveId: odspResolvedUrl.driveId,\n\t\titemId: odspResolvedUrl.itemId,\n\t};\n\tconst getAuthHeader = toInstrumentedOdspStorageTokenFetcher(\n\t\tlogger,\n\t\turlParts,\n\t\tgetStorageToken,\n\t);\n\treturn createOdspVersionManager({\n\t\turlParts,\n\t\tgetAuthHeader,\n\t\tepochTracker,\n\t\tlogger,\n\t\trequestHeaders,\n\t});\n}\n\nasync function resolveFileVersion(\n\tresolvedUrl: IResolvedUrl,\n\tfileVersion: string,\n): Promise<IResolvedUrl> {\n\tconst odspResolvedUrl = getOdspResolvedUrl(resolvedUrl);\n\tconst query = new URLSearchParams({\n\t\tdriveId: odspResolvedUrl.driveId,\n\t\titemId: odspResolvedUrl.itemId,\n\t\tfileVersion,\n\t});\n\tif (odspResolvedUrl.dataStorePath !== undefined) {\n\t\tquery.set(\"path\", odspResolvedUrl.dataStorePath);\n\t}\n\tif (odspResolvedUrl.codeHint?.containerPackageName !== undefined) {\n\t\tquery.set(\"containerPackageName\", odspResolvedUrl.codeHint.containerPackageName);\n\t}\n\treturn new OdspDriverUrlResolver().resolve({\n\t\turl: `${odspResolvedUrl.siteUrl}?${query.toString()}`,\n\t});\n}\n\n/**\n * ODSP's point-in-time document service implementation.\n *\n * @remarks Import this function from the dedicated point-in-time entrypoint and inject it through\n * {@link IOdspDocumentServiceFactoryOptions.pointInTimeDocumentServiceImplementation}.\n *\n * @param props - ODSP service dependencies and point-in-time load parameters supplied by the factory.\n * @returns A read-only ODSP document service materialized at the requested sequence number.\n *\n * @legacy @beta\n */\nexport async function createPointInTimeDocumentService(\n\tprops: IOdspPointInTimeDocumentServiceImplementationProps,\n): Promise<IDocumentService> {\n\treturn createPointInTimeDocumentServiceCore(props);\n}\n\n/**\n * Creates the point-in-time service with replaceable dependencies for testing.\n * @internal\n */\nexport async function createPointInTimeDocumentServiceCore(\n\t{\n\t\tresolvedUrl,\n\t\ttargetSequenceNumber,\n\t\tlogger,\n\t\tclientIsSummarizer,\n\t\tpersistedCache,\n\t\tgetStorageToken,\n\t\trequestHeaders,\n\t\tcreateDocumentService,\n\t}: IOdspPointInTimeDocumentServiceImplementationProps,\n\tdependencies: IPointInTimeDocumentServiceDependencies = {},\n): Promise<OdspPointInTimeDocumentService> {\n\tconst odspLogger = createOdspLogger(logger);\n\tconst extLogger = createChildLogger({ logger: odspLogger });\n\tconst odspResolvedUrl = getOdspResolvedUrl(resolvedUrl);\n\tconst cacheAndTracker = createOdspCacheAndTracker(\n\t\tpersistedCache,\n\t\tnew NonPersistentCache(),\n\t\t{\n\t\t\tresolvedUrl: odspResolvedUrl,\n\t\t\tdocId: odspResolvedUrl.hashedDocumentId,\n\t\t\tfileVersion: odspResolvedUrl.fileVersion,\n\t\t},\n\t\textLogger,\n\t\tclientIsSummarizer,\n\t\trequestHeaders,\n\t);\n\n\tconst baseSelectionStartTime = performanceNow();\n\tlet baseResult: Awaited<ReturnType<IOdspVersionManager[\"findBaseForSeq\"]>>;\n\ttry {\n\t\tconst versionManager = await (\n\t\t\tdependencies.createVersionManager ??\n\t\t\t(async (url, versionLogger, epochTracker) =>\n\t\t\t\tcreateVersionManager(\n\t\t\t\t\turl,\n\t\t\t\t\tversionLogger,\n\t\t\t\t\tepochTracker,\n\t\t\t\t\tgetStorageToken,\n\t\t\t\t\trequestHeaders,\n\t\t\t\t))\n\t\t)(odspResolvedUrl, extLogger, cacheAndTracker.epochTracker);\n\t\tbaseResult = await versionManager.findBaseForSeq(targetSequenceNumber);\n\t} catch (error) {\n\t\textLogger.sendErrorEvent(\n\t\t\t{\n\t\t\t\teventName: \"VersionMarkBaseVersionSelection\",\n\t\t\t\toutcome: \"failed\",\n\t\t\t\tduration: performanceNow() - baseSelectionStartTime,\n\t\t\t\ttargetSequenceNumber,\n\t\t\t\terrorType: (error as Partial<{ errorType: string }> | undefined)?.errorType,\n\t\t\t\tavailabilityOutcome: isILoggingError(error)\n\t\t\t\t\t? error.getTelemetryProperties().versionMarkAvailabilityOutcome\n\t\t\t\t\t: undefined,\n\t\t\t},\n\t\t\terror,\n\t\t);\n\t\tthrow error;\n\t}\n\tif (baseResult.kind === \"noBaseVersion\") {\n\t\tconst oldestResolvedSequenceDetail =\n\t\t\tbaseResult.oldestResolvedSeq === undefined\n\t\t\t\t? \"\"\n\t\t\t\t: ` The oldest resolved file version is at sequence number ${baseResult.oldestResolvedSeq}.`;\n\t\tconst error = new UsageError(\n\t\t\t`No ODSP file version is available at or before sequence number ${targetSequenceNumber}.${oldestResolvedSequenceDetail}`,\n\t\t);\n\t\terror.addTelemetryProperties({\n\t\t\tversionMarkAvailabilityOutcome: \"baseVersionMissing\",\n\t\t});\n\t\textLogger.sendErrorEvent({\n\t\t\teventName: \"VersionMarkBaseVersionSelection\",\n\t\t\toutcome: \"failed\",\n\t\t\tduration: performanceNow() - baseSelectionStartTime,\n\t\t\ttargetSequenceNumber,\n\t\t\tavailabilityOutcome: \"baseVersionMissing\",\n\t\t\toldestResolvedSequenceNumber: baseResult.oldestResolvedSeq,\n\t\t\tversionsProbed: baseResult.versionsProbed,\n\t\t\tsequenceNumberFetchCount: baseResult.sequenceNumberFetchCount,\n\t\t\terrorType: error.errorType,\n\t\t});\n\t\tthrow error;\n\t}\n\textLogger.sendPerformanceEvent({\n\t\teventName: \"VersionMarkBaseVersionSelection\",\n\t\toutcome: \"succeeded\",\n\t\tduration: performanceNow() - baseSelectionStartTime,\n\t\tversionsProbed: baseResult.versionsProbed,\n\t\tsequenceNumberFetchCount: baseResult.sequenceNumberFetchCount,\n\t});\n\n\tconst recoverableResolvedUrl = await (dependencies.resolveFileVersion ?? resolveFileVersion)(\n\t\tresolvedUrl,\n\t\tbaseResult.base.versionId,\n\t);\n\tconst recoverableDocumentService = await createDocumentService(\n\t\trecoverableResolvedUrl,\n\t\todspLogger,\n\t\tcacheAndTracker,\n\t\tclientIsSummarizer,\n\t);\n\tlet liveDocumentService: IDocumentService;\n\ttry {\n\t\tliveDocumentService = await createDocumentService(\n\t\t\tresolvedUrl,\n\t\t\todspLogger,\n\t\t\tcacheAndTracker,\n\t\t\tclientIsSummarizer,\n\t\t);\n\t} catch (error) {\n\t\ttry {\n\t\t\trecoverableDocumentService.dispose();\n\t\t} catch (disposeError) {\n\t\t\textLogger.sendErrorEvent(\n\t\t\t\t{\n\t\t\t\t\teventName: \"VersionMarkRecoverableDocumentServiceDisposeError\",\n\t\t\t\t},\n\t\t\t\tdisposeError,\n\t\t\t);\n\t\t}\n\t\tthrow error;\n\t}\n\treturn new OdspPointInTimeDocumentService(\n\t\trecoverableResolvedUrl,\n\t\trecoverableDocumentService,\n\t\tliveDocumentService,\n\t\ttargetSequenceNumber,\n\t);\n}\n"]}
@@ -1 +1 @@
1
- {"version":3,"file":"odspPointInTimeDocumentService.d.ts","sourceRoot":"","sources":["../../src/pointInTimeDriver/odspPointInTimeDocumentService.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,OAAO,EAAE,iBAAiB,EAAE,MAAM,8BAA8B,CAAC;AACjE,OAAO,EAEN,KAAK,OAAO,EACZ,KAAK,wBAAwB,EAC7B,KAAK,4BAA4B,EACjC,KAAK,gBAAgB,EACrB,KAAK,sBAAsB,EAC3B,KAAK,wBAAwB,EAC7B,KAAK,uBAAuB,EAC5B,KAAK,YAAY,EAIjB,MAAM,6CAA6C,CAAC;AA2BrD;;;;;;;;;;;;;;;;;;;;;GAqBG;AACH,qBAAa,8BACZ,SAAQ,iBAAiB,CAAC,sBAAsB,CAChD,YAAW,gBAAgB;aAGV,WAAW,EAAE,YAAY;IAGzC,OAAO,CAAC,QAAQ,CAAC,0BAA0B;IAC3C,OAAO,CAAC,QAAQ,CAAC,mBAAmB;IACpC,OAAO,CAAC,QAAQ,CAAC,oBAAoB;gBALrB,WAAW,EAAE,YAAY,EAGxB,0BAA0B,EAAE,gBAAgB,EAC5C,mBAAmB,EAAE,gBAAgB,EACrC,oBAAoB,EAAE,MAAM;IAQ9C,SAAgB,QAAQ,EAAE,wBAAwB,CAAyB;IAEpE,OAAO,IAAI,IAAI;IAMT,gBAAgB,IAAI,OAAO,CAAC,uBAAuB,CAAC;IAKpD,qBAAqB,IAAI,OAAO,CAAC,4BAA4B,CAAC;IAgB9D,oBAAoB,CAAC,OAAO,EAAE,OAAO,GAAG,OAAO,CAAC,wBAAwB,CAAC;IAStF,OAAO,CAAC,QAAQ,CAAC,qBAAqB,CAEpC;CACF"}
1
+ {"version":3,"file":"odspPointInTimeDocumentService.d.ts","sourceRoot":"","sources":["../../src/pointInTimeDriver/odspPointInTimeDocumentService.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,OAAO,EAAE,iBAAiB,EAAE,MAAM,8BAA8B,CAAC;AACjE,OAAO,EAEN,KAAK,OAAO,EACZ,KAAK,wBAAwB,EAC7B,KAAK,4BAA4B,EACjC,KAAK,gBAAgB,EACrB,KAAK,sBAAsB,EAC3B,KAAK,wBAAwB,EAC7B,KAAK,uBAAuB,EAC5B,KAAK,YAAY,EAIjB,MAAM,6CAA6C,CAAC;AA+CrD;;;;;;;;;;;;;;;;;;;;;GAqBG;AACH,qBAAa,8BACZ,SAAQ,iBAAiB,CAAC,sBAAsB,CAChD,YAAW,gBAAgB;aAGV,WAAW,EAAE,YAAY;IAGzC,OAAO,CAAC,QAAQ,CAAC,0BAA0B;IAC3C,OAAO,CAAC,QAAQ,CAAC,mBAAmB;IACpC,OAAO,CAAC,QAAQ,CAAC,oBAAoB;gBALrB,WAAW,EAAE,YAAY,EAGxB,0BAA0B,EAAE,gBAAgB,EAC5C,mBAAmB,EAAE,gBAAgB,EACrC,oBAAoB,EAAE,MAAM;IAQ9C,SAAgB,QAAQ,EAAE,wBAAwB,CAAyB;IAEpE,OAAO,IAAI,IAAI;IAMT,gBAAgB,IAAI,OAAO,CAAC,uBAAuB,CAAC;IAKpD,qBAAqB,IAAI,OAAO,CAAC,4BAA4B,CAAC;IAmC9D,oBAAoB,CAAC,OAAO,EAAE,OAAO,GAAG,OAAO,CAAC,wBAAwB,CAAC;IAStF,OAAO,CAAC,QAAQ,CAAC,qBAAqB,CAEpC;CACF"}
@@ -5,6 +5,8 @@
5
5
  import { TypedEventEmitter } from "@fluid-internal/client-utils";
6
6
  import { FetchSource, } from "@fluidframework/driver-definitions/internal";
7
7
  import { DocumentStorageServiceProxy } from "@fluidframework/driver-utils/internal";
8
+ import { OdspErrorTypes } from "@fluidframework/odsp-driver-definitions/internal";
9
+ import { isFluidError } from "@fluidframework/telemetry-utils/internal";
8
10
  /**
9
11
  * Forces point-in-time snapshot reads to bypass caches while forwarding all other storage operations
10
12
  * to the selected historical document service.
@@ -22,6 +24,21 @@ class PointInTimeDocumentStorageService extends DocumentStorageServiceProxy {
22
24
  return super.getVersions(versionId, count, scenarioName, FetchSource.noCache);
23
25
  }
24
26
  }
27
+ function annotateMissingOps(error) {
28
+ if (!isFluidError(error)) {
29
+ return;
30
+ }
31
+ // Attach a stable Version Mark availability classification only when the driver error
32
+ // confirms that the required historical ops are unavailable.
33
+ const historyIsUnavailable = error.errorType === OdspErrorTypes.cannotCatchUp ||
34
+ (error.errorType === OdspErrorTypes.genericNetworkError &&
35
+ error.getTelemetryProperties().opsFetchFailure === "tooManyRetries");
36
+ if (historyIsUnavailable) {
37
+ error.addTelemetryProperties({
38
+ versionMarkAvailabilityOutcome: "missingOps",
39
+ });
40
+ }
41
+ }
25
42
  /**
26
43
  * A read-only document service that materializes a document at a target sequence number by combining
27
44
  * a recoverable snapshot with a bounded replay of live ops.
@@ -77,7 +94,29 @@ export class OdspPointInTimeDocumentService extends TypedEventEmitter {
77
94
  // The exclusive upper bound needed to include the target op itself.
78
95
  const boundedTo = this.targetSequenceNumber + 1;
79
96
  return {
80
- fetchMessages: (from, to, abortSignal, cachedOnly, fetchReason) => liveDeltaStorage.fetchMessages(from, to === undefined ? boundedTo : Math.min(to, boundedTo), abortSignal, cachedOnly, fetchReason),
97
+ fetchMessages: (from, to, abortSignal, cachedOnly, fetchReason) => {
98
+ try {
99
+ const stream = liveDeltaStorage.fetchMessages(from, to === undefined ? boundedTo : Math.min(to, boundedTo), abortSignal, cachedOnly, fetchReason);
100
+ return {
101
+ read: async () => {
102
+ try {
103
+ return await stream.read();
104
+ }
105
+ catch (error) {
106
+ // The bounded replay owns the conclusion that these specific failures mean
107
+ // the bridge to the target cannot be materialized. Preserve the driver's raw
108
+ // errorType while attaching the stable Version Mark availability outcome.
109
+ annotateMissingOps(error);
110
+ throw error;
111
+ }
112
+ },
113
+ };
114
+ }
115
+ catch (error) {
116
+ annotateMissingOps(error);
117
+ throw error;
118
+ }
119
+ },
81
120
  };
82
121
  }
83
122
  async connectToDeltaStream(_client) {
@@ -1 +1 @@
1
- {"version":3,"file":"odspPointInTimeDocumentService.js","sourceRoot":"","sources":["../../src/pointInTimeDriver/odspPointInTimeDocumentService.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,OAAO,EAAE,iBAAiB,EAAE,MAAM,8BAA8B,CAAC;AACjE,OAAO,EACN,WAAW,GAYX,MAAM,6CAA6C,CAAC;AACrD,OAAO,EAAE,2BAA2B,EAAE,MAAM,uCAAuC,CAAC;AAEpF;;;GAGG;AACH,MAAM,iCAAkC,SAAQ,2BAA2B;IAC1D,KAAK,CAAC,WAAW,CAChC,oBAA4C;QAE5C,OAAO,KAAK,CAAC,WAAW,CAAC;YACxB,GAAG,oBAAoB;YACvB,WAAW,EAAE,WAAW,CAAC,OAAO;SAChC,CAAC,CAAC;IACJ,CAAC;IAEe,KAAK,CAAC,WAAW;IAChC,iHAAiH;IACjH,SAAwB,EACxB,KAAa,EACb,YAAqB;QAErB,OAAO,KAAK,CAAC,WAAW,CAAC,SAAS,EAAE,KAAK,EAAE,YAAY,EAAE,WAAW,CAAC,OAAO,CAAC,CAAC;IAC/E,CAAC;CACD;AAED;;;;;;;;;;;;;;;;;;;;;GAqBG;AACH,MAAM,OAAO,8BACZ,SAAQ,iBAAyC;IAIhC;IAGC;IACA;IACA;IANlB,YACiB,WAAyB;IACzC,4FAA4F;IAC5F,sFAAsF;IACrE,0BAA4C,EAC5C,mBAAqC,EACrC,oBAA4B;QAE7C,KAAK,EAAE,CAAC;QAPQ,gBAAW,GAAX,WAAW,CAAc;QAGxB,+BAA0B,GAA1B,0BAA0B,CAAkB;QAC5C,wBAAmB,GAAnB,mBAAmB,CAAkB;QACrC,yBAAoB,GAApB,oBAAoB,CAAQ;QAG7C,IAAI,CAAC,mBAAmB,CAAC,EAAE,CAAC,gBAAgB,EAAE,IAAI,CAAC,qBAAqB,CAAC,CAAC;IAC3E,CAAC;IAED,+FAA+F;IAC/F,qEAAqE;IACrD,QAAQ,GAA6B,EAAE,WAAW,EAAE,IAAI,EAAE,CAAC;IAEpE,OAAO;QACb,IAAI,CAAC,mBAAmB,CAAC,GAAG,CAAC,gBAAgB,EAAE,IAAI,CAAC,qBAAqB,CAAC,CAAC;QAC3E,IAAI,CAAC,0BAA0B,CAAC,OAAO,EAAE,CAAC;QAC1C,IAAI,CAAC,mBAAmB,CAAC,OAAO,EAAE,CAAC;IACpC,CAAC;IAEM,KAAK,CAAC,gBAAgB;QAC5B,MAAM,OAAO,GAAG,MAAM,IAAI,CAAC,0BAA0B,CAAC,gBAAgB,EAAE,CAAC;QACzE,OAAO,IAAI,iCAAiC,CAAC,OAAO,CAAC,CAAC;IACvD,CAAC;IAEM,KAAK,CAAC,qBAAqB;QACjC,MAAM,gBAAgB,GAAG,MAAM,IAAI,CAAC,mBAAmB,CAAC,qBAAqB,EAAE,CAAC;QAChF,oEAAoE;QACpE,MAAM,SAAS,GAAG,IAAI,CAAC,oBAAoB,GAAG,CAAC,CAAC;QAChD,OAAO;YACN,aAAa,EAAE,CAAC,IAAI,EAAE,EAAE,EAAE,WAAW,EAAE,UAAU,EAAE,WAAW,EAAE,EAAE,CACjE,gBAAgB,CAAC,aAAa,CAC7B,IAAI,EACJ,EAAE,KAAK,SAAS,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC,IAAI,CAAC,GAAG,CAAC,EAAE,EAAE,SAAS,CAAC,EACtD,WAAW,EACX,UAAU,EACV,WAAW,CACX;SACF,CAAC;IACH,CAAC;IAEM,KAAK,CAAC,oBAAoB,CAAC,OAAgB;QACjD,0FAA0F;QAC1F,yFAAyF;QACzF,8DAA8D;QAC9D,MAAM,IAAI,KAAK,CACd,2FAA2F,CAC3F,CAAC;IACH,CAAC;IAEgB,qBAAqB,GAAG,CAAC,QAAgC,EAAQ,EAAE;QACnF,IAAI,CAAC,IAAI,CAAC,gBAAgB,EAAE,QAAQ,CAAC,CAAC;IACvC,CAAC,CAAC;CACF","sourcesContent":["/*!\n * Copyright (c) Microsoft Corporation and contributors. All rights reserved.\n * Licensed under the MIT License.\n */\n\nimport { TypedEventEmitter } from \"@fluid-internal/client-utils\";\nimport {\n\tFetchSource,\n\ttype IClient,\n\ttype IDocumentDeltaConnection,\n\ttype IDocumentDeltaStorageService,\n\ttype IDocumentService,\n\ttype IDocumentServiceEvents,\n\ttype IDocumentServicePolicies,\n\ttype IDocumentStorageService,\n\ttype IResolvedUrl,\n\ttype ISnapshot,\n\ttype ISnapshotFetchOptions,\n\ttype IVersion,\n} from \"@fluidframework/driver-definitions/internal\";\nimport { DocumentStorageServiceProxy } from \"@fluidframework/driver-utils/internal\";\n\n/**\n * Forces point-in-time snapshot reads to bypass caches while forwarding all other storage operations\n * to the selected historical document service.\n */\nclass PointInTimeDocumentStorageService extends DocumentStorageServiceProxy {\n\tpublic override async getSnapshot(\n\t\tsnapshotFetchOptions?: ISnapshotFetchOptions,\n\t): Promise<ISnapshot> {\n\t\treturn super.getSnapshot({\n\t\t\t...snapshotFetchOptions,\n\t\t\tfetchSource: FetchSource.noCache,\n\t\t});\n\t}\n\n\tpublic override async getVersions(\n\t\t// eslint-disable-next-line @rushstack/no-new-null -- IDocumentStorageService is a legacy API that requires null.\n\t\tversionId: string | null,\n\t\tcount: number,\n\t\tscenarioName?: string,\n\t): Promise<IVersion[]> {\n\t\treturn super.getVersions(versionId, count, scenarioName, FetchSource.noCache);\n\t}\n}\n\n/**\n * A read-only document service that materializes a document at a target sequence number by combining\n * a recoverable snapshot with a bounded replay of live ops.\n *\n * @remarks\n * Storage (the snapshot) is served from the closest file version at or before the target sequence\n * number. The ops needed to advance from that snapshot to the target are read from the live\n * document's delta storage, bounded so that no op past the target is ever fetched.\n *\n * The service advertises the {@link IDocumentServicePolicies.storageOnly} policy. This reuses the\n * loader's \"frozen\" load mechanism: the connection manager synthesizes a read-only frozen delta\n * stream instead of opening a live socket, and forces the container read-only. The delta manager\n * still catches up from the snapshot's sequence number through delta storage, which is exactly the\n * bounded replay we want. As a result no live delta-stream connection is ever established.\n *\n * Op availability is enforced by the delta storage stack itself: it validates that fetched batches\n * are contiguous from the requested start, keeps requesting until the bounded range is fully\n * delivered, and fails the fetch if the ops never materialize. So a stream that completes has\n * necessarily served the whole bridge, and no additional checks are needed here.\n *\n * @internal\n */\nexport class OdspPointInTimeDocumentService\n\textends TypedEventEmitter<IDocumentServiceEvents>\n\timplements IDocumentService\n{\n\tpublic constructor(\n\t\tpublic readonly resolvedUrl: IResolvedUrl,\n\t\t// Serves the snapshot: a read-only document service bound to the closest file version at or\n\t\t// before the target sequence number. Its storage is the base we replay ops on top of.\n\t\tprivate readonly recoverableDocumentService: IDocumentService,\n\t\tprivate readonly liveDocumentService: IDocumentService,\n\t\tprivate readonly targetSequenceNumber: number,\n\t) {\n\t\tsuper();\n\t\tthis.liveDocumentService.on(\"metadataUpdate\", this.metadataUpdateHandler);\n\t}\n\n\t// storageOnly makes the connection manager synthesize a read-only frozen delta stream (no live\n\t// socket) and force the container read-only - see the class remarks.\n\tpublic readonly policies: IDocumentServicePolicies = { storageOnly: true };\n\n\tpublic dispose(): void {\n\t\tthis.liveDocumentService.off(\"metadataUpdate\", this.metadataUpdateHandler);\n\t\tthis.recoverableDocumentService.dispose();\n\t\tthis.liveDocumentService.dispose();\n\t}\n\n\tpublic async connectToStorage(): Promise<IDocumentStorageService> {\n\t\tconst storage = await this.recoverableDocumentService.connectToStorage();\n\t\treturn new PointInTimeDocumentStorageService(storage);\n\t}\n\n\tpublic async connectToDeltaStorage(): Promise<IDocumentDeltaStorageService> {\n\t\tconst liveDeltaStorage = await this.liveDocumentService.connectToDeltaStorage();\n\t\t// The exclusive upper bound needed to include the target op itself.\n\t\tconst boundedTo = this.targetSequenceNumber + 1;\n\t\treturn {\n\t\t\tfetchMessages: (from, to, abortSignal, cachedOnly, fetchReason) =>\n\t\t\t\tliveDeltaStorage.fetchMessages(\n\t\t\t\t\tfrom,\n\t\t\t\t\tto === undefined ? boundedTo : Math.min(to, boundedTo),\n\t\t\t\t\tabortSignal,\n\t\t\t\t\tcachedOnly,\n\t\t\t\t\tfetchReason,\n\t\t\t\t),\n\t\t};\n\t}\n\n\tpublic async connectToDeltaStream(_client: IClient): Promise<IDocumentDeltaConnection> {\n\t\t// Unreachable under normal flow: the connection manager short-circuits on the storageOnly\n\t\t// policy and synthesizes a frozen delta stream before ever calling connectToDeltaStream.\n\t\t// Reaching here indicates a regression of that short-circuit.\n\t\tthrow new Error(\n\t\t\t\"OdspPointInTimeDocumentService is storage-only; connectToDeltaStream should not be called\",\n\t\t);\n\t}\n\n\tprivate readonly metadataUpdateHandler = (metadata: Record<string, string>): void => {\n\t\tthis.emit(\"metadataUpdate\", metadata);\n\t};\n}\n"]}
1
+ {"version":3,"file":"odspPointInTimeDocumentService.js","sourceRoot":"","sources":["../../src/pointInTimeDriver/odspPointInTimeDocumentService.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\nimport { TypedEventEmitter } from \"@fluid-internal/client-utils\";\nimport {\n\tFetchSource,\n\ttype IClient,\n\ttype IDocumentDeltaConnection,\n\ttype IDocumentDeltaStorageService,\n\ttype IDocumentService,\n\ttype IDocumentServiceEvents,\n\ttype IDocumentServicePolicies,\n\ttype IDocumentStorageService,\n\ttype IResolvedUrl,\n\ttype ISnapshot,\n\ttype ISnapshotFetchOptions,\n\ttype IVersion,\n} from \"@fluidframework/driver-definitions/internal\";\nimport { DocumentStorageServiceProxy } from \"@fluidframework/driver-utils/internal\";\nimport { OdspErrorTypes } from \"@fluidframework/odsp-driver-definitions/internal\";\nimport { isFluidError } from \"@fluidframework/telemetry-utils/internal\";\n\n/**\n * Forces point-in-time snapshot reads to bypass caches while forwarding all other storage operations\n * to the selected historical document service.\n */\nclass PointInTimeDocumentStorageService extends DocumentStorageServiceProxy {\n\tpublic override async getSnapshot(\n\t\tsnapshotFetchOptions?: ISnapshotFetchOptions,\n\t): Promise<ISnapshot> {\n\t\treturn super.getSnapshot({\n\t\t\t...snapshotFetchOptions,\n\t\t\tfetchSource: FetchSource.noCache,\n\t\t});\n\t}\n\n\tpublic override async getVersions(\n\t\t// eslint-disable-next-line @rushstack/no-new-null -- IDocumentStorageService is a legacy API that requires null.\n\t\tversionId: string | null,\n\t\tcount: number,\n\t\tscenarioName?: string,\n\t): Promise<IVersion[]> {\n\t\treturn super.getVersions(versionId, count, scenarioName, FetchSource.noCache);\n\t}\n}\n\nfunction annotateMissingOps(error: unknown): void {\n\tif (!isFluidError(error)) {\n\t\treturn;\n\t}\n\n\t// Attach a stable Version Mark availability classification only when the driver error\n\t// confirms that the required historical ops are unavailable.\n\tconst historyIsUnavailable =\n\t\terror.errorType === OdspErrorTypes.cannotCatchUp ||\n\t\t(error.errorType === OdspErrorTypes.genericNetworkError &&\n\t\t\terror.getTelemetryProperties().opsFetchFailure === \"tooManyRetries\");\n\tif (historyIsUnavailable) {\n\t\terror.addTelemetryProperties({\n\t\t\tversionMarkAvailabilityOutcome: \"missingOps\",\n\t\t});\n\t}\n}\n\n/**\n * A read-only document service that materializes a document at a target sequence number by combining\n * a recoverable snapshot with a bounded replay of live ops.\n *\n * @remarks\n * Storage (the snapshot) is served from the closest file version at or before the target sequence\n * number. The ops needed to advance from that snapshot to the target are read from the live\n * document's delta storage, bounded so that no op past the target is ever fetched.\n *\n * The service advertises the {@link IDocumentServicePolicies.storageOnly} policy. This reuses the\n * loader's \"frozen\" load mechanism: the connection manager synthesizes a read-only frozen delta\n * stream instead of opening a live socket, and forces the container read-only. The delta manager\n * still catches up from the snapshot's sequence number through delta storage, which is exactly the\n * bounded replay we want. As a result no live delta-stream connection is ever established.\n *\n * Op availability is enforced by the delta storage stack itself: it validates that fetched batches\n * are contiguous from the requested start, keeps requesting until the bounded range is fully\n * delivered, and fails the fetch if the ops never materialize. So a stream that completes has\n * necessarily served the whole bridge, and no additional checks are needed here.\n *\n * @internal\n */\nexport class OdspPointInTimeDocumentService\n\textends TypedEventEmitter<IDocumentServiceEvents>\n\timplements IDocumentService\n{\n\tpublic constructor(\n\t\tpublic readonly resolvedUrl: IResolvedUrl,\n\t\t// Serves the snapshot: a read-only document service bound to the closest file version at or\n\t\t// before the target sequence number. Its storage is the base we replay ops on top of.\n\t\tprivate readonly recoverableDocumentService: IDocumentService,\n\t\tprivate readonly liveDocumentService: IDocumentService,\n\t\tprivate readonly targetSequenceNumber: number,\n\t) {\n\t\tsuper();\n\t\tthis.liveDocumentService.on(\"metadataUpdate\", this.metadataUpdateHandler);\n\t}\n\n\t// storageOnly makes the connection manager synthesize a read-only frozen delta stream (no live\n\t// socket) and force the container read-only - see the class remarks.\n\tpublic readonly policies: IDocumentServicePolicies = { storageOnly: true };\n\n\tpublic dispose(): void {\n\t\tthis.liveDocumentService.off(\"metadataUpdate\", this.metadataUpdateHandler);\n\t\tthis.recoverableDocumentService.dispose();\n\t\tthis.liveDocumentService.dispose();\n\t}\n\n\tpublic async connectToStorage(): Promise<IDocumentStorageService> {\n\t\tconst storage = await this.recoverableDocumentService.connectToStorage();\n\t\treturn new PointInTimeDocumentStorageService(storage);\n\t}\n\n\tpublic async connectToDeltaStorage(): Promise<IDocumentDeltaStorageService> {\n\t\tconst liveDeltaStorage = await this.liveDocumentService.connectToDeltaStorage();\n\t\t// The exclusive upper bound needed to include the target op itself.\n\t\tconst boundedTo = this.targetSequenceNumber + 1;\n\t\treturn {\n\t\t\tfetchMessages: (from, to, abortSignal, cachedOnly, fetchReason) => {\n\t\t\t\ttry {\n\t\t\t\t\tconst stream = liveDeltaStorage.fetchMessages(\n\t\t\t\t\t\tfrom,\n\t\t\t\t\t\tto === undefined ? boundedTo : Math.min(to, boundedTo),\n\t\t\t\t\t\tabortSignal,\n\t\t\t\t\t\tcachedOnly,\n\t\t\t\t\t\tfetchReason,\n\t\t\t\t\t);\n\t\t\t\t\treturn {\n\t\t\t\t\t\tread: async () => {\n\t\t\t\t\t\t\ttry {\n\t\t\t\t\t\t\t\treturn await stream.read();\n\t\t\t\t\t\t\t} catch (error) {\n\t\t\t\t\t\t\t\t// The bounded replay owns the conclusion that these specific failures mean\n\t\t\t\t\t\t\t\t// the bridge to the target cannot be materialized. Preserve the driver's raw\n\t\t\t\t\t\t\t\t// errorType while attaching the stable Version Mark availability outcome.\n\t\t\t\t\t\t\t\tannotateMissingOps(error);\n\t\t\t\t\t\t\t\tthrow error;\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t},\n\t\t\t\t\t};\n\t\t\t\t} catch (error) {\n\t\t\t\t\tannotateMissingOps(error);\n\t\t\t\t\tthrow error;\n\t\t\t\t}\n\t\t\t},\n\t\t};\n\t}\n\n\tpublic async connectToDeltaStream(_client: IClient): Promise<IDocumentDeltaConnection> {\n\t\t// Unreachable under normal flow: the connection manager short-circuits on the storageOnly\n\t\t// policy and synthesizes a frozen delta stream before ever calling connectToDeltaStream.\n\t\t// Reaching here indicates a regression of that short-circuit.\n\t\tthrow new Error(\n\t\t\t\"OdspPointInTimeDocumentService is storage-only; connectToDeltaStream should not be called\",\n\t\t);\n\t}\n\n\tprivate readonly metadataUpdateHandler = (metadata: Record<string, string>): void => {\n\t\tthis.emit(\"metadataUpdate\", metadata);\n\t};\n}\n"]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@fluidframework/odsp-driver",
3
- "version": "3.2.0",
3
+ "version": "3.3.0",
4
4
  "description": "Socket storage implementation for SPO and ODC",
5
5
  "homepage": "https://fluidframework.com",
6
6
  "repository": {
@@ -66,27 +66,27 @@
66
66
  "temp-directory": "nyc/.nyc_output"
67
67
  },
68
68
  "dependencies": {
69
- "@fluid-internal/client-utils": "~3.2.0",
70
- "@fluidframework/core-interfaces": "~3.2.0",
71
- "@fluidframework/core-utils": "~3.2.0",
72
- "@fluidframework/driver-base": "~3.2.0",
73
- "@fluidframework/driver-definitions": "~3.2.0",
74
- "@fluidframework/driver-utils": "~3.2.0",
75
- "@fluidframework/odsp-doclib-utils": "~3.2.0",
76
- "@fluidframework/odsp-driver-definitions": "~3.2.0",
77
- "@fluidframework/telemetry-utils": "~3.2.0",
69
+ "@fluid-internal/client-utils": "~3.3.0",
70
+ "@fluidframework/core-interfaces": "~3.3.0",
71
+ "@fluidframework/core-utils": "~3.3.0",
72
+ "@fluidframework/driver-base": "~3.3.0",
73
+ "@fluidframework/driver-definitions": "~3.3.0",
74
+ "@fluidframework/driver-utils": "~3.3.0",
75
+ "@fluidframework/odsp-doclib-utils": "~3.3.0",
76
+ "@fluidframework/odsp-driver-definitions": "~3.3.0",
77
+ "@fluidframework/telemetry-utils": "~3.3.0",
78
78
  "socket.io-client": "^4.8.3",
79
79
  "uuid": "^11.1.0"
80
80
  },
81
81
  "devDependencies": {
82
82
  "@arethetypeswrong/cli": "^0.18.5",
83
83
  "@biomejs/biome": "~2.4.5",
84
- "@fluid-internal/mocha-test-setup": "~3.2.0",
84
+ "@fluid-internal/mocha-test-setup": "~3.3.0",
85
85
  "@fluid-tools/build-cli": "^0.67.0",
86
86
  "@fluidframework/build-common": "^2.0.3",
87
87
  "@fluidframework/build-tools": "^0.67.0",
88
88
  "@fluidframework/eslint-config-fluid": "^14.0.0",
89
- "@fluidframework/odsp-driver-previous": "npm:@fluidframework/odsp-driver@3.1.0",
89
+ "@fluidframework/odsp-driver-previous": "npm:@fluidframework/odsp-driver@3.2.0",
90
90
  "@microsoft/api-extractor": "7.58.1",
91
91
  "@types/mocha": "^10.0.10",
92
92
  "@types/node": "~22.19.17",
@@ -54,6 +54,10 @@ export type BaseForSeq =
54
54
  /** A recoverable version with `sequenceNumber <= target` was found. */
55
55
  readonly kind: "found";
56
56
  readonly base: ResolvedVersion;
57
+ /** Number of sealed version candidates examined before selecting the base. */
58
+ readonly versionsProbed: number;
59
+ /** Number of candidate sequence numbers that required an uncached ODSP fetch. */
60
+ readonly sequenceNumberFetchCount: number;
57
61
  }
58
62
  | {
59
63
  /**
@@ -63,6 +67,10 @@ export type BaseForSeq =
63
67
  readonly kind: "noBaseVersion";
64
68
  /** The oldest sequence number that was resolved while searching, if any. */
65
69
  readonly oldestResolvedSeq?: number;
70
+ /** Number of sealed version candidates examined before exhausting retained history. */
71
+ readonly versionsProbed: number;
72
+ /** Number of candidate sequence numbers that required an uncached ODSP fetch. */
73
+ readonly sequenceNumberFetchCount: number;
66
74
  };
67
75
 
68
76
  /**
@@ -111,8 +119,13 @@ export class OdspVersionManager implements IOdspVersionManager {
111
119
  const candidates = versions.slice(1);
112
120
 
113
121
  let oldestResolvedSeq: number | undefined;
122
+ let versionsProbed = 0;
123
+ let sequenceNumberFetchCount = 0;
114
124
  for (const version of candidates) {
115
- const sequenceNumber = await this.resolveSeq(version.versionId);
125
+ versionsProbed++;
126
+ const sequenceNumber = await this.resolveSeq(version.versionId, () => {
127
+ sequenceNumberFetchCount++;
128
+ });
116
129
  oldestResolvedSeq =
117
130
  oldestResolvedSeq === undefined
118
131
  ? sequenceNumber
@@ -121,10 +134,15 @@ export class OdspVersionManager implements IOdspVersionManager {
121
134
  const base = { ...version, sequenceNumber };
122
135
  // Confirm the chosen base shares the live document's lineage before handing it back
123
136
  await this.validateLineageEpoch(base);
124
- return { kind: "found", base };
137
+ return { kind: "found", base, versionsProbed, sequenceNumberFetchCount };
125
138
  }
126
139
  }
127
- return { kind: "noBaseVersion", oldestResolvedSeq };
140
+ return {
141
+ kind: "noBaseVersion",
142
+ oldestResolvedSeq,
143
+ versionsProbed,
144
+ sequenceNumberFetchCount,
145
+ };
128
146
  }
129
147
 
130
148
  private async validateLineageEpoch(base: ResolvedVersion): Promise<void> {
@@ -159,6 +177,7 @@ export class OdspVersionManager implements IOdspVersionManager {
159
177
  driverVersion,
160
178
  serverEpoch: liveEpoch,
161
179
  clientEpoch: baseEpoch,
180
+ versionMarkAvailabilityOutcome: "lineageMismatch",
162
181
  },
163
182
  );
164
183
  }
@@ -181,12 +200,13 @@ export class OdspVersionManager implements IOdspVersionManager {
181
200
  );
182
201
  }
183
202
 
184
- private async resolveSeq(versionId: string): Promise<number> {
203
+ private async resolveSeq(versionId: string, onCacheMiss?: () => void): Promise<number> {
185
204
  // Cached indefinitely (a sealed version's number is fixed); concurrent calls coalesce and a failed
186
205
  // resolution is evicted so a later call retries.
187
- return this.seqCache.addOrGet(versionId, async () =>
188
- this.fetcher.resolveSequenceNumber(versionId),
189
- );
206
+ return this.seqCache.addOrGet(versionId, async () => {
207
+ onCacheMiss?.();
208
+ return this.fetcher.resolveSequenceNumber(versionId);
209
+ });
190
210
  }
191
211
 
192
212
  private async resolveVersionEpoch(versionId: string): Promise<string | undefined> {
@@ -6,4 +6,4 @@
6
6
  */
7
7
 
8
8
  export const pkgName = "@fluidframework/odsp-driver";
9
- export const pkgVersion = "3.2.0";
9
+ export const pkgVersion = "3.3.0";
@@ -3,6 +3,7 @@
3
3
  * Licensed under the MIT License.
4
4
  */
5
5
 
6
+ import { performanceNow } from "@fluid-internal/client-utils";
6
7
  import type {
7
8
  IDocumentService,
8
9
  IResolvedUrl,
@@ -14,6 +15,7 @@ import type {
14
15
  } from "@fluidframework/odsp-driver-definitions/internal";
15
16
  import {
16
17
  createChildLogger,
18
+ isILoggingError,
17
19
  type TelemetryLoggerExt,
18
20
  } from "@fluidframework/telemetry-utils/internal";
19
21
 
@@ -143,21 +145,68 @@ export async function createPointInTimeDocumentServiceCore(
143
145
  requestHeaders,
144
146
  );
145
147
 
146
- const versionManager = await (
147
- dependencies.createVersionManager ??
148
- (async (url, versionLogger, epochTracker) =>
149
- createVersionManager(url, versionLogger, epochTracker, getStorageToken, requestHeaders))
150
- )(odspResolvedUrl, extLogger, cacheAndTracker.epochTracker);
151
- const baseResult = await versionManager.findBaseForSeq(targetSequenceNumber);
148
+ const baseSelectionStartTime = performanceNow();
149
+ let baseResult: Awaited<ReturnType<IOdspVersionManager["findBaseForSeq"]>>;
150
+ try {
151
+ const versionManager = await (
152
+ dependencies.createVersionManager ??
153
+ (async (url, versionLogger, epochTracker) =>
154
+ createVersionManager(
155
+ url,
156
+ versionLogger,
157
+ epochTracker,
158
+ getStorageToken,
159
+ requestHeaders,
160
+ ))
161
+ )(odspResolvedUrl, extLogger, cacheAndTracker.epochTracker);
162
+ baseResult = await versionManager.findBaseForSeq(targetSequenceNumber);
163
+ } catch (error) {
164
+ extLogger.sendErrorEvent(
165
+ {
166
+ eventName: "VersionMarkBaseVersionSelection",
167
+ outcome: "failed",
168
+ duration: performanceNow() - baseSelectionStartTime,
169
+ targetSequenceNumber,
170
+ errorType: (error as Partial<{ errorType: string }> | undefined)?.errorType,
171
+ availabilityOutcome: isILoggingError(error)
172
+ ? error.getTelemetryProperties().versionMarkAvailabilityOutcome
173
+ : undefined,
174
+ },
175
+ error,
176
+ );
177
+ throw error;
178
+ }
152
179
  if (baseResult.kind === "noBaseVersion") {
153
180
  const oldestResolvedSequenceDetail =
154
181
  baseResult.oldestResolvedSeq === undefined
155
182
  ? ""
156
183
  : ` The oldest resolved file version is at sequence number ${baseResult.oldestResolvedSeq}.`;
157
- throw new UsageError(
184
+ const error = new UsageError(
158
185
  `No ODSP file version is available at or before sequence number ${targetSequenceNumber}.${oldestResolvedSequenceDetail}`,
159
186
  );
187
+ error.addTelemetryProperties({
188
+ versionMarkAvailabilityOutcome: "baseVersionMissing",
189
+ });
190
+ extLogger.sendErrorEvent({
191
+ eventName: "VersionMarkBaseVersionSelection",
192
+ outcome: "failed",
193
+ duration: performanceNow() - baseSelectionStartTime,
194
+ targetSequenceNumber,
195
+ availabilityOutcome: "baseVersionMissing",
196
+ oldestResolvedSequenceNumber: baseResult.oldestResolvedSeq,
197
+ versionsProbed: baseResult.versionsProbed,
198
+ sequenceNumberFetchCount: baseResult.sequenceNumberFetchCount,
199
+ errorType: error.errorType,
200
+ });
201
+ throw error;
160
202
  }
203
+ extLogger.sendPerformanceEvent({
204
+ eventName: "VersionMarkBaseVersionSelection",
205
+ outcome: "succeeded",
206
+ duration: performanceNow() - baseSelectionStartTime,
207
+ versionsProbed: baseResult.versionsProbed,
208
+ sequenceNumberFetchCount: baseResult.sequenceNumberFetchCount,
209
+ });
161
210
 
162
211
  const recoverableResolvedUrl = await (dependencies.resolveFileVersion ?? resolveFileVersion)(
163
212
  resolvedUrl,
@@ -182,7 +231,9 @@ export async function createPointInTimeDocumentServiceCore(
182
231
  recoverableDocumentService.dispose();
183
232
  } catch (disposeError) {
184
233
  extLogger.sendErrorEvent(
185
- { eventName: "PointInTimeRecoverableDocumentServiceDisposeError" },
234
+ {
235
+ eventName: "VersionMarkRecoverableDocumentServiceDisposeError",
236
+ },
186
237
  disposeError,
187
238
  );
188
239
  }
@@ -19,6 +19,8 @@ import {
19
19
  type IVersion,
20
20
  } from "@fluidframework/driver-definitions/internal";
21
21
  import { DocumentStorageServiceProxy } from "@fluidframework/driver-utils/internal";
22
+ import { OdspErrorTypes } from "@fluidframework/odsp-driver-definitions/internal";
23
+ import { isFluidError } from "@fluidframework/telemetry-utils/internal";
22
24
 
23
25
  /**
24
26
  * Forces point-in-time snapshot reads to bypass caches while forwarding all other storage operations
@@ -44,6 +46,24 @@ class PointInTimeDocumentStorageService extends DocumentStorageServiceProxy {
44
46
  }
45
47
  }
46
48
 
49
+ function annotateMissingOps(error: unknown): void {
50
+ if (!isFluidError(error)) {
51
+ return;
52
+ }
53
+
54
+ // Attach a stable Version Mark availability classification only when the driver error
55
+ // confirms that the required historical ops are unavailable.
56
+ const historyIsUnavailable =
57
+ error.errorType === OdspErrorTypes.cannotCatchUp ||
58
+ (error.errorType === OdspErrorTypes.genericNetworkError &&
59
+ error.getTelemetryProperties().opsFetchFailure === "tooManyRetries");
60
+ if (historyIsUnavailable) {
61
+ error.addTelemetryProperties({
62
+ versionMarkAvailabilityOutcome: "missingOps",
63
+ });
64
+ }
65
+ }
66
+
47
67
  /**
48
68
  * A read-only document service that materializes a document at a target sequence number by combining
49
69
  * a recoverable snapshot with a bounded replay of live ops.
@@ -102,14 +122,33 @@ export class OdspPointInTimeDocumentService
102
122
  // The exclusive upper bound needed to include the target op itself.
103
123
  const boundedTo = this.targetSequenceNumber + 1;
104
124
  return {
105
- fetchMessages: (from, to, abortSignal, cachedOnly, fetchReason) =>
106
- liveDeltaStorage.fetchMessages(
107
- from,
108
- to === undefined ? boundedTo : Math.min(to, boundedTo),
109
- abortSignal,
110
- cachedOnly,
111
- fetchReason,
112
- ),
125
+ fetchMessages: (from, to, abortSignal, cachedOnly, fetchReason) => {
126
+ try {
127
+ const stream = liveDeltaStorage.fetchMessages(
128
+ from,
129
+ to === undefined ? boundedTo : Math.min(to, boundedTo),
130
+ abortSignal,
131
+ cachedOnly,
132
+ fetchReason,
133
+ );
134
+ return {
135
+ read: async () => {
136
+ try {
137
+ return await stream.read();
138
+ } catch (error) {
139
+ // The bounded replay owns the conclusion that these specific failures mean
140
+ // the bridge to the target cannot be materialized. Preserve the driver's raw
141
+ // errorType while attaching the stable Version Mark availability outcome.
142
+ annotateMissingOps(error);
143
+ throw error;
144
+ }
145
+ },
146
+ };
147
+ } catch (error) {
148
+ annotateMissingOps(error);
149
+ throw error;
150
+ }
151
+ },
113
152
  };
114
153
  }
115
154