@fluidframework/odsp-driver 2.114.0 → 2.115.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.
- package/CHANGELOG.md +4 -0
- package/api-extractor/api-extractor-lint-legacyAlpha.cjs.json +5 -0
- package/api-extractor/api-extractor-lint-legacyAlpha.esm.json +5 -0
- package/api-extractor/api-extractor.legacy.json +5 -1
- package/api-report/odsp-driver.legacy.alpha.api.md +232 -0
- package/dist/getUrlAndHeadersWithAuth.d.ts +4 -0
- package/dist/getUrlAndHeadersWithAuth.d.ts.map +1 -1
- package/dist/getUrlAndHeadersWithAuth.js +4 -0
- package/dist/getUrlAndHeadersWithAuth.js.map +1 -1
- package/dist/index.d.ts +3 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +7 -3
- package/dist/index.js.map +1 -1
- package/dist/legacy.d.ts +1 -1
- package/dist/legacyAlpha.d.ts +49 -0
- package/dist/odspVersionManager/odspFileVersionFetcher.d.ts +40 -1
- package/dist/odspVersionManager/odspFileVersionFetcher.d.ts.map +1 -1
- package/dist/odspVersionManager/odspFileVersionFetcher.js +52 -14
- package/dist/odspVersionManager/odspFileVersionFetcher.js.map +1 -1
- package/dist/odspVersionManager/odspVersionManager.d.ts +23 -45
- package/dist/odspVersionManager/odspVersionManager.d.ts.map +1 -1
- package/dist/odspVersionManager/odspVersionManager.js +68 -33
- package/dist/odspVersionManager/odspVersionManager.js.map +1 -1
- package/dist/packageVersion.d.ts +1 -1
- package/dist/packageVersion.js +1 -1
- package/dist/packageVersion.js.map +1 -1
- package/dist/pointInTimeDriver/odspPointInTimeDocumentService.d.ts +5 -0
- package/dist/pointInTimeDriver/odspPointInTimeDocumentService.d.ts.map +1 -1
- package/dist/pointInTimeDriver/odspPointInTimeDocumentService.js +6 -3
- package/dist/pointInTimeDriver/odspPointInTimeDocumentService.js.map +1 -1
- package/dist/pointInTimeDriver/odspPointInTimeDocumentServiceFactory.d.ts +25 -33
- package/dist/pointInTimeDriver/odspPointInTimeDocumentServiceFactory.d.ts.map +1 -1
- package/dist/pointInTimeDriver/odspPointInTimeDocumentServiceFactory.js +62 -38
- package/dist/pointInTimeDriver/odspPointInTimeDocumentServiceFactory.js.map +1 -1
- package/dist/public.d.ts +1 -1
- package/internal.d.ts +1 -1
- package/legacy/alpha.d.ts +11 -0
- package/legacy.d.ts +1 -1
- package/lib/getUrlAndHeadersWithAuth.d.ts +4 -0
- package/lib/getUrlAndHeadersWithAuth.d.ts.map +1 -1
- package/lib/getUrlAndHeadersWithAuth.js +4 -0
- package/lib/getUrlAndHeadersWithAuth.js.map +1 -1
- package/lib/index.d.ts +3 -2
- package/lib/index.d.ts.map +1 -1
- package/lib/index.js +5 -3
- package/lib/index.js.map +1 -1
- package/lib/legacy.d.ts +1 -1
- package/lib/legacyAlpha.d.ts +49 -0
- package/lib/odspVersionManager/odspFileVersionFetcher.d.ts +40 -1
- package/lib/odspVersionManager/odspFileVersionFetcher.d.ts.map +1 -1
- package/lib/odspVersionManager/odspFileVersionFetcher.js +53 -15
- package/lib/odspVersionManager/odspFileVersionFetcher.js.map +1 -1
- package/lib/odspVersionManager/odspVersionManager.d.ts +23 -45
- package/lib/odspVersionManager/odspVersionManager.d.ts.map +1 -1
- package/lib/odspVersionManager/odspVersionManager.js +68 -33
- package/lib/odspVersionManager/odspVersionManager.js.map +1 -1
- package/lib/packageVersion.d.ts +1 -1
- package/lib/packageVersion.js +1 -1
- package/lib/packageVersion.js.map +1 -1
- package/lib/pointInTimeDriver/odspPointInTimeDocumentService.d.ts +5 -0
- package/lib/pointInTimeDriver/odspPointInTimeDocumentService.d.ts.map +1 -1
- package/lib/pointInTimeDriver/odspPointInTimeDocumentService.js +6 -3
- package/lib/pointInTimeDriver/odspPointInTimeDocumentService.js.map +1 -1
- package/lib/pointInTimeDriver/odspPointInTimeDocumentServiceFactory.d.ts +25 -33
- package/lib/pointInTimeDriver/odspPointInTimeDocumentServiceFactory.d.ts.map +1 -1
- package/lib/pointInTimeDriver/odspPointInTimeDocumentServiceFactory.js +62 -38
- package/lib/pointInTimeDriver/odspPointInTimeDocumentServiceFactory.js.map +1 -1
- package/lib/public.d.ts +1 -1
- package/package.json +26 -14
- package/src/getUrlAndHeadersWithAuth.ts +4 -0
- package/src/index.ts +8 -3
- package/src/odspVersionManager/DEV.md +376 -54
- package/src/odspVersionManager/odspFileVersionFetcher.ts +111 -16
- package/src/odspVersionManager/odspVersionManager.ts +106 -68
- package/src/packageVersion.ts +1 -1
- package/src/pointInTimeDriver/odspPointInTimeDocumentService.ts +8 -4
- package/src/pointInTimeDriver/odspPointInTimeDocumentServiceFactory.ts +123 -48
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"odspFileVersionFetcher.js","sourceRoot":"","sources":["../../src/odspVersionManager/odspFileVersionFetcher.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 * An {@link IOdspFileVersionFetcher} backed by the ODSP REST APIs:\n * - GET /_api/v2.1/.../versions -- enumerate the file's versions.\n * - GET /_api/v2.1/.../versions/{label}/opStream/snapshots/trees/latest?blobs=2 -- fetch a version's\n * snapshot and read its sequence number, parsed with the driver's snapshot parser.\n */\n\nimport type {\n\tIOdspUrlParts,\n\tInstrumentedStorageTokenFetcher,\n} from \"@fluidframework/odsp-driver-definitions/internal\";\nimport type { TelemetryLoggerExt } from \"@fluidframework/telemetry-utils/internal\";\n\nimport { currentReadVersion, parseCompactSnapshotResponse } from \"../compactSnapshotParser.js\";\nimport type { IOdspSnapshot } from \"../contracts.js\";\nimport type { EpochTracker } from \"../epochTracker.js\";\nimport { getHeadersWithAuth } from \"../getUrlAndHeadersWithAuth.js\";\nimport { convertOdspSnapshotToSnapshotTreeAndBlobs } from \"../odspSnapshotParser.js\";\nimport { getApiRoot } from \"../odspUrlHelper.js\";\nimport { getWithRetryForTokenRefresh } from \"../odspUtils.js\";\n\nimport type { OdspFileVersionRef, IOdspFileVersionFetcher } from \"./odspVersionManager.js\";\n\n/**\n * Raw shape of a OneDrive/SharePoint driveItem version (an entry in the `/versions` response).\n * @see https://learn.microsoft.com/en-us/onedrive/developer/rest-api/resources/driveitemversion\n */\ninterface IDriveItemVersion {\n\t/** The version's label, e.g. \"42.0\". */\n\treadonly id: string;\n\treadonly lastModifiedDateTime: string;\n}\n\n/** A single page of the driveItem `/versions` response. */\ninterface IDriveItemVersionsPage {\n\treadonly value?: IDriveItemVersion[];\n\t/** Absolute URL of the next page, present only while more versions remain. */\n\treadonly \"@odata.nextLink\"?: string;\n}\n\n/**\n * Inputs needed to make authenticated requests against a specific ODSP file.\n */\nexport interface OdspFileVersionFetcherProps {\n\treadonly urlParts: IOdspUrlParts;\n\treadonly getAuthHeader: InstrumentedStorageTokenFetcher;\n\treadonly epochTracker: EpochTracker;\n\treadonly logger: TelemetryLoggerExt;\n}\n\n/**\n * Create an {@link IOdspFileVersionFetcher} that talks to a specific ODSP file.\n */\nexport function createOdspFileVersionFetcher(\n\tprops: OdspFileVersionFetcherProps,\n): IOdspFileVersionFetcher {\n\tconst { urlParts, getAuthHeader, epochTracker, logger } = props;\n\tconst { siteUrl, driveId, itemId } = urlParts;\n\n\tconst listFileVersions = async (): Promise<OdspFileVersionRef[]> =>\n\t\tgetWithRetryForTokenRefresh(async (options) => {\n\t\t\tconst method = \"GET\";\n\t\t\tconst versions: OdspFileVersionRef[] = [];\n\t\t\t// The file's version history (distinct from the driver's snapshot list), from the same API\n\t\t\t// root as the snapshot call so consumer (ODC) and enterprise (SPO) hosts are handled alike.\n\t\t\t// A long history is paged, so follow `@odata.nextLink` until it is absent; otherwise a base\n\t\t\t// version beyond the first page would be missed and wrongly reported as \"no base version\".\n\t\t\tlet url = `${getApiRoot(new URL(siteUrl))}/drives/${driveId}/items/${itemId}/versions`;\n\t\t\tdo {\n\t\t\t\tconst token = await getAuthHeader(\n\t\t\t\t\t{ ...options, request: { url, method } },\n\t\t\t\t\t\"FileVersions\",\n\t\t\t\t);\n\t\t\t\tconst headers = getHeadersWithAuth(token);\n\t\t\t\tconst response = await epochTracker.fetchAndParseAsJSON<{\n\t\t\t\t\tvalue?: IDriveItemVersion[];\n\t\t\t\t}>(url, { method, headers }, \"versions\");\n\t\t\t\tconst page = response.content as IDriveItemVersionsPage;\n\t\t\t\t// The API returns versions newest-first.\n\t\t\t\tfor (const version of page.value ?? []) {\n\t\t\t\t\tversions.push({\n\t\t\t\t\t\tversionId: version.id,\n\t\t\t\t\t\tlastModifiedDateTime: version.lastModifiedDateTime,\n\t\t\t\t\t});\n\t\t\t\t}\n\t\t\t\turl = page[\"@odata.nextLink\"] ?? \"\";\n\t\t\t} while (url);\n\t\t\treturn versions;\n\t\t});\n\n\tconst resolveSequenceNumber = async (versionId: string): Promise<number> =>\n\t\tgetWithRetryForTokenRefresh(async (options) => {\n\t\t\t// A file version's sequence number lives inside that version's snapshot, so fetch the snapshot\n\t\t\t// from the version-scoped endpoint. `blobs=2` inlines blob contents so the `.protocol/attributes`\n\t\t\t// blob (which carries the sequence number) is included; `deltas=1` is intentionally omitted, as\n\t\t\t// it would bundle the op stream and its op-level sequence numbers.\n\t\t\tconst url = `${getApiRoot(new URL(siteUrl))}/drives/${driveId}/items/${itemId}/versions/${encodeURIComponent(\n\t\t\t\tversionId,\n\t\t\t)}/opStream/snapshots/trees/latest?blobs=2`;\n\t\t\tconst method = \"GET\";\n\t\t\tconst token = await getAuthHeader(\n\t\t\t\t{ ...options, request: { url, method } },\n\t\t\t\t\"FileVersionSnapshot\",\n\t\t\t);\n\t\t\tconst headers = getHeadersWithAuth(token);\n\t\t\t// The server can return the snapshot in one of two equivalent framings: verbose JSON, or\n\t\t\t// \"ms-fluid\" — ODSP's compact binary encoding of the same snapshot. Advertise both, and pin the\n\t\t\t// binary format version (as the driver's own snapshot fetch does) so the server cannot hand back\n\t\t\t// a binary version this code's parser does not understand.\n\t\t\theaders.accept = `application/json, application/ms-fluid; v=${currentReadVersion}`;\n\t\t\tconst response = await epochTracker.fetch(url, { method, headers }, \"treesLatest\");\n\t\t\tconst contentType = response.headers.get(\"content-type\") ?? \"\";\n\t\t\tlet sequenceNumber: number | undefined;\n\t\t\tif (contentType.includes(\"application/json\")) {\n\t\t\t\t// JSON framing: read it with the driver's JSON snapshot parser.\n\t\t\t\tconst snapshotJson = (await response.content.json()) as IOdspSnapshot;\n\t\t\t\tsequenceNumber =\n\t\t\t\t\tconvertOdspSnapshotToSnapshotTreeAndBlobs(snapshotJson).sequenceNumber;\n\t\t\t} else {\n\t\t\t\t// ms-fluid framing: the compact binary form; read it with the driver's compact-snapshot parser.\n\t\t\t\tconst bytes = new Uint8Array(await response.content.arrayBuffer());\n\t\t\t\tsequenceNumber = parseCompactSnapshotResponse(bytes, logger).sequenceNumber;\n\t\t\t}\n\t\t\t// A version's snapshot must carry a sequence number; a missing one is surfaced as an error\n\t\t\t// naming the version, rather than returning a wrong value.\n\t\t\tif (sequenceNumber === undefined) {\n\t\t\t\tthrow new Error(`ODSP file version ${versionId} snapshot is missing a sequenceNumber`);\n\t\t\t}\n\t\t\treturn sequenceNumber;\n\t\t});\n\n\treturn { listFileVersions, resolveSequenceNumber };\n}\n"]}
|
|
1
|
+
{"version":3,"file":"odspFileVersionFetcher.js","sourceRoot":"","sources":["../../src/odspVersionManager/odspFileVersionFetcher.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 * An {@link IOdspFileVersionFetcher} backed by the ODSP REST APIs:\n * - GET /_api/v2.1/.../versions -- enumerate the file's versions.\n * - GET /_api/v2.1/.../versions/{label}/opStream/snapshots/trees/latest?blobs=2 -- fetch a version's\n * snapshot and read its sequence number, parsed with the driver's snapshot parser.\n * - GET /_api/v2.1/.../[versions/{label}/]opStream/snapshots/trees/latest?blobs=0 -- read a version's\n * or the live document's ODSP epoch (`x-fluid-epoch`) to compare their lineage.\n */\n\nimport { NonRetryableError } from \"@fluidframework/driver-utils/internal\";\nimport { OdspErrorTypes } from \"@fluidframework/odsp-driver-definitions/internal\";\nimport type {\n\tIOdspUrlParts,\n\tInstrumentedStorageTokenFetcher,\n} from \"@fluidframework/odsp-driver-definitions/internal\";\nimport type { TelemetryLoggerExt } from \"@fluidframework/telemetry-utils/internal\";\n\nimport { currentReadVersion, parseCompactSnapshotResponse } from \"../compactSnapshotParser.js\";\nimport type { IOdspSnapshot } from \"../contracts.js\";\nimport type { EpochTracker } from \"../epochTracker.js\";\nimport { getHeadersWithAuth } from \"../getUrlAndHeadersWithAuth.js\";\nimport { convertOdspSnapshotToSnapshotTreeAndBlobs } from \"../odspSnapshotParser.js\";\nimport { getApiRoot } from \"../odspUrlHelper.js\";\nimport { fetchArray, getWithRetryForTokenRefresh } from \"../odspUtils.js\";\nimport { pkgVersion as driverVersion } from \"../packageVersion.js\";\n\n/**\n * A single ODSP file version, as listed by the file's version history.\n */\nexport interface OdspFileVersionRef {\n\t/**\n\t * The version's label (e.g. `\"42.0\"`), used to address the version when fetching it.\n\t */\n\treadonly versionId: string;\n\t/**\n\t * Last-modified timestamp of this version, ISO-8601.\n\t */\n\treadonly lastModifiedDateTime: string;\n}\n\n/**\n * Provides a file's versions and resolves each version's Fluid sequence number. Injected into\n * the version manager so the selection logic does not depend on how versions are fetched.\n */\nexport interface IOdspFileVersionFetcher {\n\t/**\n\t * Enumerate the file's versions, newest-first.\n\t */\n\tlistFileVersions(): Promise<OdspFileVersionRef[]>;\n\t/**\n\t * Resolve a single version's Fluid sequence number. Throws on failure rather than returning a\n\t * wrong value.\n\t */\n\tresolveSequenceNumber(versionId: string): Promise<number>;\n\t/**\n\t * Read the live document's current ODSP epoch (`x-fluid-epoch`), or `undefined`. Epoch identifies\n\t * the file's binary lineage and changes on a version restore or download-then-reupload; compared\n\t * with {@link IOdspFileVersionFetcher.getRecoverableVersionEpoch} to confirm a base is on the live\n\t * document's lineage.\n\t */\n\tgetLiveDocumentEpoch(): Promise<string | undefined>;\n\t/**\n\t * Read the ODSP epoch of a specific file version, or `undefined`. See\n\t * {@link IOdspFileVersionFetcher.getLiveDocumentEpoch}.\n\t */\n\tgetRecoverableVersionEpoch(versionId: string): Promise<string | undefined>;\n}\n\n/**\n * Raw shape of a OneDrive/SharePoint driveItem version (an entry in the `/versions` response).\n * @see https://learn.microsoft.com/en-us/onedrive/developer/rest-api/resources/driveitemversion\n */\ninterface IDriveItemVersion {\n\t/** The version's label, e.g. \"42.0\". */\n\treadonly id: string;\n\treadonly lastModifiedDateTime: string;\n}\n\n/** A single page of the driveItem `/versions` response. */\ninterface IDriveItemVersionsPage {\n\treadonly value?: IDriveItemVersion[];\n\t/** Absolute URL of the next page, present only while more versions remain. */\n\treadonly \"@odata.nextLink\"?: string;\n}\n\n/**\n * Inputs needed to make authenticated requests against a specific ODSP file.\n */\nexport interface OdspFileVersionFetcherProps {\n\treadonly urlParts: IOdspUrlParts;\n\treadonly getAuthHeader: InstrumentedStorageTokenFetcher;\n\treadonly epochTracker: EpochTracker;\n\treadonly logger: TelemetryLoggerExt;\n}\n\n/**\n * Create an {@link IOdspFileVersionFetcher} that talks to a specific ODSP file.\n */\nexport function createOdspFileVersionFetcher(\n\tprops: OdspFileVersionFetcherProps,\n): IOdspFileVersionFetcher {\n\tconst { urlParts, getAuthHeader, epochTracker, logger } = props;\n\tconst { siteUrl, driveId, itemId } = urlParts;\n\n\tconst listFileVersions = async (): Promise<OdspFileVersionRef[]> =>\n\t\tgetWithRetryForTokenRefresh(async (options) => {\n\t\t\tconst method = \"GET\";\n\t\t\tconst versions: OdspFileVersionRef[] = [];\n\t\t\t// The file's version history (distinct from the driver's snapshot list), from the same API\n\t\t\t// root as the snapshot call so consumer (ODC) and enterprise (SPO) hosts are handled alike.\n\t\t\t// A long history is paged, so follow `@odata.nextLink` until it is absent; otherwise a base\n\t\t\t// version beyond the first page would be missed and wrongly reported as \"no base version\".\n\t\t\tlet url = `${getApiRoot(new URL(siteUrl))}/drives/${driveId}/items/${itemId}/versions`;\n\t\t\tdo {\n\t\t\t\tconst token = await getAuthHeader(\n\t\t\t\t\t{ ...options, request: { url, method } },\n\t\t\t\t\t\"FileVersions\",\n\t\t\t\t);\n\t\t\t\tconst headers = getHeadersWithAuth(token);\n\t\t\t\tconst response = await epochTracker.fetchAndParseAsJSON<{\n\t\t\t\t\tvalue?: IDriveItemVersion[];\n\t\t\t\t}>(url, { method, headers }, \"versions\");\n\t\t\t\tconst page = response.content as IDriveItemVersionsPage;\n\t\t\t\t// The API returns versions newest-first.\n\t\t\t\tfor (const version of page.value ?? []) {\n\t\t\t\t\tversions.push({\n\t\t\t\t\t\tversionId: version.id,\n\t\t\t\t\t\tlastModifiedDateTime: version.lastModifiedDateTime,\n\t\t\t\t\t});\n\t\t\t\t}\n\t\t\t\turl = page[\"@odata.nextLink\"] ?? \"\";\n\t\t\t} while (url);\n\t\t\treturn versions;\n\t\t});\n\n\tconst resolveSequenceNumber = async (versionId: string): Promise<number> =>\n\t\tgetWithRetryForTokenRefresh(async (options) => {\n\t\t\t// The sequence number lives in the version snapshot's `.protocol/attributes` blob, so fetch the\n\t\t\t// version-scoped snapshot with `blobs=2` to inline it. No op stream needed.\n\t\t\tconst url = `${getApiRoot(new URL(siteUrl))}/drives/${driveId}/items/${itemId}/versions/${encodeURIComponent(\n\t\t\t\tversionId,\n\t\t\t)}/opStream/snapshots/trees/latest?blobs=2`;\n\t\t\tconst method = \"GET\";\n\t\t\tconst token = await getAuthHeader(\n\t\t\t\t{ ...options, request: { url, method } },\n\t\t\t\t\"FileVersionSnapshot\",\n\t\t\t);\n\t\t\tconst headers = getHeadersWithAuth(token);\n\t\t\t// The snapshot comes back as JSON or \"ms-fluid\" (ODSP's compact binary form). Accept both and\n\t\t\t// pin the binary version (as the driver's snapshot fetch does) so the parser can read it.\n\t\t\theaders.accept = `application/json, application/ms-fluid; v=${currentReadVersion}`;\n\t\t\tconst response = await epochTracker.fetch(url, { method, headers }, \"treesLatest\");\n\t\t\tconst contentType = response.headers.get(\"content-type\") ?? \"\";\n\t\t\tlet sequenceNumber: number | undefined;\n\t\t\tif (contentType.includes(\"application/json\")) {\n\t\t\t\t// JSON framing: read it with the driver's JSON snapshot parser.\n\t\t\t\tconst snapshotJson = (await response.content.json()) as IOdspSnapshot;\n\t\t\t\tsequenceNumber =\n\t\t\t\t\tconvertOdspSnapshotToSnapshotTreeAndBlobs(snapshotJson).sequenceNumber;\n\t\t\t} else if (contentType.includes(\"application/ms-fluid\")) {\n\t\t\t\t// ms-fluid framing: the compact binary form; read it with the driver's compact-snapshot parser.\n\t\t\t\tconst bytes = new Uint8Array(await response.content.arrayBuffer());\n\t\t\t\tsequenceNumber = parseCompactSnapshotResponse(bytes, logger).sequenceNumber;\n\t\t\t} else {\n\t\t\t\t// Neither framing (e.g. an HTML error page). Throw the driver's typed bad-response error\n\t\t\t\t// (like fetchSnapshot.ts): canRetry=false stops the loader re-driving, while the\n\t\t\t\t// incorrectServerResponse errorType still earns one wire-retry from getWithRetryForTokenRefresh.\n\t\t\t\tthrow new NonRetryableError(\n\t\t\t\t\t`ODSP file version ${versionId} snapshot returned an unexpected content-type`,\n\t\t\t\t\tOdspErrorTypes.incorrectServerResponse,\n\t\t\t\t\t{ driverVersion, contentType, accept: headers.accept },\n\t\t\t\t);\n\t\t\t}\n\t\t\t// The sequence number must be a non-negative integer; a missing or malformed one throws the same\n\t\t\t// typed error as above rather than feeding a wrong value into base selection.\n\t\t\tif (\n\t\t\t\t!(\n\t\t\t\t\ttypeof sequenceNumber === \"number\" &&\n\t\t\t\t\tNumber.isInteger(sequenceNumber) &&\n\t\t\t\t\tsequenceNumber >= 0\n\t\t\t\t)\n\t\t\t) {\n\t\t\t\tthrow new NonRetryableError(\n\t\t\t\t\t`ODSP file version ${versionId} snapshot has a missing or invalid sequenceNumber (${String(sequenceNumber)})`,\n\t\t\t\t\tOdspErrorTypes.incorrectServerResponse,\n\t\t\t\t\t{ driverVersion, contentType, accept: headers.accept },\n\t\t\t\t);\n\t\t\t}\n\t\t\treturn sequenceNumber;\n\t\t});\n\n\tconst itemRoot = `${getApiRoot(new URL(siteUrl))}/drives/${driveId}/items/${itemId}`;\n\n\t// Reads the `x-fluid-epoch` header from `url`. Deliberately uses the raw fetch helper instead of\n\t// `epochTracker.fetch`: the whole point is to COMPARE the base version's epoch against the live\n\t// document's epoch, but the shared EpochTracker pins to the first epoch it sees and throws on the\n\t// second (divergent) read - so it could never yield two epochs to compare. `fetchArray` also lets\n\t// the body (JSON or ms-fluid binary) be consumed and discarded; only the header is needed.\n\tconst readEpoch = async (url: string, scenarioName: string): Promise<string | undefined> =>\n\t\tgetWithRetryForTokenRefresh(async (options) => {\n\t\t\tconst method = \"GET\";\n\t\t\tconst token = await getAuthHeader(\n\t\t\t\t{ ...options, request: { url, method } },\n\t\t\t\tscenarioName,\n\t\t\t);\n\t\t\tconst headers = getHeadersWithAuth(token);\n\t\t\tconst response = await fetchArray(url, { method, headers });\n\t\t\treturn response.headers.get(\"x-fluid-epoch\") ?? undefined;\n\t\t});\n\n\tconst getLiveDocumentEpoch = async (): Promise<string | undefined> =>\n\t\t// The (unversioned) live snapshot endpoint is a current-file read, so its epoch is the live\n\t\t// document's epoch. `blobs=0` keeps the response to the tree metadata.\n\t\treadEpoch(`${itemRoot}/opStream/snapshots/trees/latest?blobs=0`, \"LiveEpoch\");\n\n\tconst getRecoverableVersionEpoch = async (versionId: string): Promise<string | undefined> =>\n\t\treadEpoch(\n\t\t\t`${itemRoot}/versions/${encodeURIComponent(versionId)}/opStream/snapshots/trees/latest?blobs=0`,\n\t\t\t\"FileVersionEpoch\",\n\t\t);\n\n\treturn {\n\t\tlistFileVersions,\n\t\tresolveSequenceNumber,\n\t\tgetLiveDocumentEpoch,\n\t\tgetRecoverableVersionEpoch,\n\t};\n}\n"]}
|
|
@@ -2,27 +2,8 @@
|
|
|
2
2
|
* Copyright (c) Microsoft Corporation and contributors. All rights reserved.
|
|
3
3
|
* Licensed under the MIT License.
|
|
4
4
|
*/
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
* the base to load or replay from when materializing a document at a point in time.
|
|
8
|
-
*
|
|
9
|
-
* The selection logic depends on an injected {@link IOdspFileVersionFetcher}, so it is independent of
|
|
10
|
-
* how versions are enumerated and resolved (real ODSP, a test double, or an alternative backend).
|
|
11
|
-
*/
|
|
12
|
-
import { type OdspFileVersionFetcherProps } from "./odspFileVersionFetcher.js";
|
|
13
|
-
/**
|
|
14
|
-
* A single ODSP file version, as listed by the file's version history.
|
|
15
|
-
*/
|
|
16
|
-
export interface OdspFileVersionRef {
|
|
17
|
-
/**
|
|
18
|
-
* The version's label (e.g. `"42.0"`), used to address the version when fetching it.
|
|
19
|
-
*/
|
|
20
|
-
readonly versionId: string;
|
|
21
|
-
/**
|
|
22
|
-
* Last-modified timestamp of this version, ISO-8601.
|
|
23
|
-
*/
|
|
24
|
-
readonly lastModifiedDateTime: string;
|
|
25
|
-
}
|
|
5
|
+
import { type OdspFileVersionFetcherProps, type OdspFileVersionRef, type IOdspFileVersionFetcher } from "./odspFileVersionFetcher.js";
|
|
6
|
+
export type { OdspFileVersionRef, IOdspFileVersionFetcher } from "./odspFileVersionFetcher.js";
|
|
26
7
|
/**
|
|
27
8
|
* An ODSP file version together with its resolved Fluid sequence number.
|
|
28
9
|
*/
|
|
@@ -36,35 +17,25 @@ export interface ResolvedVersion extends OdspFileVersionRef {
|
|
|
36
17
|
* Result of resolving the base version for a target sequence number.
|
|
37
18
|
*
|
|
38
19
|
* @remarks
|
|
39
|
-
*
|
|
40
|
-
*
|
|
41
|
-
*
|
|
20
|
+
* The tip (newest) version is excluded from base selection, so when the target is at or after the head
|
|
21
|
+
* the base is the newest *sealed* version with `seq <= target` (a normal `found`); if the file's only
|
|
22
|
+
* version is the tip, the result is `noBaseVersion`. The wired consumer surfaces `noBaseVersion` as a
|
|
23
|
+
* `UsageError`; loading the live file for a near-head target is a possible future consumer choice, not
|
|
24
|
+
* current behavior.
|
|
42
25
|
*/
|
|
43
26
|
export type BaseForSeq = {
|
|
44
27
|
/** A recoverable version with `sequenceNumber <= target` was found. */
|
|
45
28
|
readonly kind: "found";
|
|
46
29
|
readonly base: ResolvedVersion;
|
|
47
30
|
} | {
|
|
48
|
-
/**
|
|
31
|
+
/**
|
|
32
|
+
* No sealed version has `sequenceNumber <= target` — the target predates retained history, or
|
|
33
|
+
* the only version is the excluded tip.
|
|
34
|
+
*/
|
|
49
35
|
readonly kind: "noBaseVersion";
|
|
50
36
|
/** The oldest sequence number that was resolved while searching, if any. */
|
|
51
37
|
readonly oldestResolvedSeq?: number;
|
|
52
38
|
};
|
|
53
|
-
/**
|
|
54
|
-
* Provides a file's versions and resolves each version's Fluid sequence number. Injected into
|
|
55
|
-
* the version manager so the selection logic does not depend on how versions are fetched.
|
|
56
|
-
*/
|
|
57
|
-
export interface IOdspFileVersionFetcher {
|
|
58
|
-
/**
|
|
59
|
-
* Enumerate the file's versions, newest-first.
|
|
60
|
-
*/
|
|
61
|
-
listFileVersions(): Promise<OdspFileVersionRef[]>;
|
|
62
|
-
/**
|
|
63
|
-
* Resolve a single version's Fluid sequence number. Throws on failure rather than returning a
|
|
64
|
-
* wrong value.
|
|
65
|
-
*/
|
|
66
|
-
resolveSequenceNumber(versionId: string): Promise<number>;
|
|
67
|
-
}
|
|
68
39
|
/**
|
|
69
40
|
* Selects the file version to use as the base for loading or replaying to a target sequence number.
|
|
70
41
|
*/
|
|
@@ -72,24 +43,31 @@ export interface IOdspVersionManager {
|
|
|
72
43
|
/**
|
|
73
44
|
* Given a target sequence number, return the closest version at or before it (`found`), or
|
|
74
45
|
* `noBaseVersion` if the target predates the oldest retained version.
|
|
46
|
+
*
|
|
47
|
+
* @remarks
|
|
48
|
+
* A `found` base is guaranteed to share the live document's ODSP epoch (lineage): before returning
|
|
49
|
+
* it, the chosen base's epoch is compared with the live document's, and a mismatch throws a non-retryable error
|
|
50
|
+
* rather than returning a base that cannot be replayed. Op availability is enforced separately and
|
|
51
|
+
* lazily as the loader reads the bridging ops.
|
|
75
52
|
*/
|
|
76
53
|
findBaseForSeq(target: number): Promise<BaseForSeq>;
|
|
77
54
|
}
|
|
78
55
|
/**
|
|
79
|
-
* Default {@link IOdspVersionManager}. Caches
|
|
56
|
+
* Default {@link IOdspVersionManager}. Caches resolved sequence numbers (which never change); the version
|
|
57
|
+
* list is re-enumerated on each query rather than cached, since new versions are cut over time. The
|
|
80
58
|
* resolution strategy (eager, newest-to-oldest, stopping at the first usable base) is hidden behind
|
|
81
59
|
* {@link findBaseForSeq} and can change without affecting callers.
|
|
82
60
|
*/
|
|
83
61
|
export declare class OdspVersionManager implements IOdspVersionManager {
|
|
84
62
|
private readonly fetcher;
|
|
85
|
-
private
|
|
86
|
-
private readonly
|
|
63
|
+
private readonly seqCache;
|
|
64
|
+
private readonly epochCache;
|
|
87
65
|
constructor(fetcher: IOdspFileVersionFetcher);
|
|
88
|
-
refresh(): void;
|
|
89
66
|
findBaseForSeq(target: number): Promise<BaseForSeq>;
|
|
67
|
+
private validateLineageEpoch;
|
|
90
68
|
listVersions(): Promise<ResolvedVersion[]>;
|
|
91
|
-
private getVersions;
|
|
92
69
|
private resolveSeq;
|
|
70
|
+
private resolveVersionEpoch;
|
|
93
71
|
}
|
|
94
72
|
/**
|
|
95
73
|
* Create an {@link IOdspVersionManager} for a specific ODSP file, wired to the real ODSP REST APIs.
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"odspVersionManager.d.ts","sourceRoot":"","sources":["../../src/odspVersionManager/odspVersionManager.ts"],"names":[],"mappings":"AAAA;;;GAGG;
|
|
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"}
|
|
@@ -12,31 +12,37 @@ exports.createOdspVersionManager = exports.OdspVersionManager = void 0;
|
|
|
12
12
|
* The selection logic depends on an injected {@link IOdspFileVersionFetcher}, so it is independent of
|
|
13
13
|
* how versions are enumerated and resolved (real ODSP, a test double, or an alternative backend).
|
|
14
14
|
*/
|
|
15
|
+
const internal_1 = require("@fluidframework/core-utils/internal");
|
|
16
|
+
const internal_2 = require("@fluidframework/driver-utils/internal");
|
|
17
|
+
const internal_3 = require("@fluidframework/odsp-driver-definitions/internal");
|
|
18
|
+
const packageVersion_js_1 = require("../packageVersion.js");
|
|
15
19
|
const odspFileVersionFetcher_js_1 = require("./odspFileVersionFetcher.js");
|
|
16
20
|
/**
|
|
17
|
-
* Default {@link IOdspVersionManager}. Caches
|
|
21
|
+
* Default {@link IOdspVersionManager}. Caches resolved sequence numbers (which never change); the version
|
|
22
|
+
* list is re-enumerated on each query rather than cached, since new versions are cut over time. The
|
|
18
23
|
* resolution strategy (eager, newest-to-oldest, stopping at the first usable base) is hidden behind
|
|
19
24
|
* {@link findBaseForSeq} and can change without affecting callers.
|
|
20
25
|
*/
|
|
26
|
+
// Exported only so the same-package tests can construct it with a fake IOdspFileVersionFetcher.
|
|
27
|
+
// Deliberately kept out of the folder barrel and the package public index, so it is not public API.
|
|
21
28
|
class OdspVersionManager {
|
|
22
29
|
constructor(fetcher) {
|
|
23
30
|
this.fetcher = fetcher;
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
31
|
+
// Sealed versions' sequence numbers, memoized so each is resolved at most once per manager instance
|
|
32
|
+
// (a sealed version's number is fixed once the version exists).
|
|
33
|
+
this.seqCache = new internal_1.PromiseCache();
|
|
34
|
+
// Sealed versions' ODSP epochs, memoized like their sequence numbers. The live document's epoch is NOT
|
|
35
|
+
// cached — it can change (restore/reupload), so validateLineageEpoch always reads it fresh.
|
|
36
|
+
this.epochCache = new internal_1.PromiseCache();
|
|
29
37
|
}
|
|
30
38
|
async findBaseForSeq(target) {
|
|
31
|
-
//
|
|
32
|
-
const versions = await this.
|
|
39
|
+
// Re-enumerate the list each call (it changes as new versions are cut).
|
|
40
|
+
const versions = await this.fetcher.listFileVersions();
|
|
41
|
+
// Start past the tip (index 0): the newest version's sequence number can still advance until a newer
|
|
42
|
+
// version is cut, so it is treated as the live head rather than a stable base. Scan the remaining
|
|
43
|
+
// (sealed) versions newest-first and return the first with sequence number <= target — the closest
|
|
44
|
+
// base — or noBaseVersion, reporting the oldest sequence number seen.
|
|
33
45
|
const candidates = versions.slice(1);
|
|
34
|
-
// Versions are listed newest-first, and version order is expected to track sequence number, so
|
|
35
|
-
// the first candidate whose seq is at or before the target is taken as the closest base. Because
|
|
36
|
-
// any base at or before the target replays forward to the same state, this early stop is an
|
|
37
|
-
// optimization, not a correctness requirement: if version order and sequence order ever diverge,
|
|
38
|
-
// a base that is valid but not strictly the closest may be chosen.
|
|
39
|
-
// Scanning newest-first also yields the newest of versions sharing a sequence number (dedup).
|
|
40
46
|
let oldestResolvedSeq;
|
|
41
47
|
for (const version of candidates) {
|
|
42
48
|
const sequenceNumber = await this.resolveSeq(version.versionId);
|
|
@@ -45,35 +51,64 @@ class OdspVersionManager {
|
|
|
45
51
|
? sequenceNumber
|
|
46
52
|
: Math.min(oldestResolvedSeq, sequenceNumber);
|
|
47
53
|
if (sequenceNumber <= target) {
|
|
48
|
-
|
|
54
|
+
const base = { ...version, sequenceNumber };
|
|
55
|
+
// Confirm the chosen base shares the live document's lineage before handing it back
|
|
56
|
+
await this.validateLineageEpoch(base);
|
|
57
|
+
return { kind: "found", base };
|
|
49
58
|
}
|
|
50
59
|
}
|
|
51
60
|
return { kind: "noBaseVersion", oldestResolvedSeq };
|
|
52
61
|
}
|
|
62
|
+
async validateLineageEpoch(base) {
|
|
63
|
+
// The live document's epoch can change (a restore or download-and-reupload bumps it), so it is
|
|
64
|
+
// always read fresh. A numbered version's snapshot is immutable, so its epoch never changes and
|
|
65
|
+
// is cached per versionId (see resolveVersionEpoch).
|
|
66
|
+
const [liveEpoch, baseEpoch] = await Promise.all([
|
|
67
|
+
this.fetcher.getLiveDocumentEpoch(),
|
|
68
|
+
this.resolveVersionEpoch(base.versionId),
|
|
69
|
+
]);
|
|
70
|
+
if (liveEpoch === undefined || baseEpoch === undefined) {
|
|
71
|
+
throw new internal_2.NonRetryableError(`Cannot verify that ODSP file version ${base.versionId} shares the live document's ` +
|
|
72
|
+
`lineage: the storage response is missing an epoch (base epoch: ${baseEpoch ?? "unknown"}, ` +
|
|
73
|
+
`live epoch: ${liveEpoch ?? "unknown"}).`, internal_3.OdspErrorTypes.incorrectServerResponse, {
|
|
74
|
+
driverVersion: packageVersion_js_1.pkgVersion,
|
|
75
|
+
serverEpoch: liveEpoch,
|
|
76
|
+
clientEpoch: baseEpoch,
|
|
77
|
+
});
|
|
78
|
+
}
|
|
79
|
+
if (liveEpoch !== baseEpoch) {
|
|
80
|
+
throw new internal_2.NonRetryableError(`ODSP file version ${base.versionId} is on epoch "${baseEpoch}" but the live document is ` +
|
|
81
|
+
`on epoch "${liveEpoch}". A binary file change (e.g. a version restore or ` +
|
|
82
|
+
`download-and-reupload) renumbered the op stream, so ops cannot be replayed from this ` +
|
|
83
|
+
`base onto the live document.`, internal_3.OdspErrorTypes.fileOverwrittenInStorage, {
|
|
84
|
+
driverVersion: packageVersion_js_1.pkgVersion,
|
|
85
|
+
serverEpoch: liveEpoch,
|
|
86
|
+
clientEpoch: baseEpoch,
|
|
87
|
+
});
|
|
88
|
+
}
|
|
89
|
+
}
|
|
53
90
|
async listVersions() {
|
|
54
|
-
const versions = await this.
|
|
55
|
-
// Resolution order does not matter
|
|
91
|
+
const versions = await this.fetcher.listFileVersions();
|
|
92
|
+
// Resolution order does not matter, so resolve concurrently; the newest-first array order is
|
|
56
93
|
// preserved by Promise.all regardless of completion order.
|
|
57
|
-
return Promise.all(versions.map(async (version) => ({
|
|
94
|
+
return Promise.all(versions.map(async (version, index) => ({
|
|
58
95
|
...version,
|
|
59
|
-
|
|
96
|
+
// Resolve the tip (index 0) fresh each call, since its sequence number can still change;
|
|
97
|
+
// sealed versions come from the cache.
|
|
98
|
+
sequenceNumber: index === 0
|
|
99
|
+
? await this.fetcher.resolveSequenceNumber(version.versionId)
|
|
100
|
+
: await this.resolveSeq(version.versionId),
|
|
60
101
|
})));
|
|
61
102
|
}
|
|
62
|
-
async getVersions() {
|
|
63
|
-
// Cache the pending promise, not the awaited value, so concurrent callers share one fetch and a
|
|
64
|
-
// refresh() that runs while the fetch is in flight is not overwritten when the fetch settles.
|
|
65
|
-
this.versionsCache ??= this.fetcher.listFileVersions();
|
|
66
|
-
return this.versionsCache;
|
|
67
|
-
}
|
|
68
103
|
async resolveSeq(versionId) {
|
|
69
|
-
//
|
|
70
|
-
//
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
return
|
|
104
|
+
// Cached indefinitely (a sealed version's number is fixed); concurrent calls coalesce and a failed
|
|
105
|
+
// resolution is evicted so a later call retries.
|
|
106
|
+
return this.seqCache.addOrGet(versionId, async () => this.fetcher.resolveSequenceNumber(versionId));
|
|
107
|
+
}
|
|
108
|
+
async resolveVersionEpoch(versionId) {
|
|
109
|
+
// Cached like resolveSeq (a sealed version's epoch is fixed). The live document's epoch is read
|
|
110
|
+
// fresh instead (see validateLineageEpoch).
|
|
111
|
+
return this.epochCache.addOrGet(versionId, async () => this.fetcher.getRecoverableVersionEpoch(versionId));
|
|
77
112
|
}
|
|
78
113
|
}
|
|
79
114
|
exports.OdspVersionManager = OdspVersionManager;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"odspVersionManager.js","sourceRoot":"","sources":["../../src/odspVersionManager/odspVersionManager.ts"],"names":[],"mappings":";AAAA;;;GAGG;;;AAEH;;;;;;GAMG;AAEH,2EAGqC;AA0ErC;;;;GAIG;AACH,MAAa,kBAAkB;IAI9B,YAAoC,OAAgC;QAAhC,YAAO,GAAP,OAAO,CAAyB;QAFnD,iBAAY,GAAG,IAAI,GAAG,EAA2B,CAAC;IAEI,CAAC;IAEjE,OAAO;QACb,IAAI,CAAC,aAAa,GAAG,SAAS,CAAC;QAC/B,IAAI,CAAC,YAAY,CAAC,KAAK,EAAE,CAAC;IAC3B,CAAC;IAEM,KAAK,CAAC,cAAc,CAAC,MAAc;QACzC,4FAA4F;QAC5F,MAAM,QAAQ,GAAG,MAAM,IAAI,CAAC,WAAW,EAAE,CAAC;QAC1C,MAAM,UAAU,GAAG,QAAQ,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC;QAErC,+FAA+F;QAC/F,iGAAiG;QACjG,4FAA4F;QAC5F,iGAAiG;QACjG,mEAAmE;QACnE,8FAA8F;QAC9F,IAAI,iBAAqC,CAAC;QAC1C,KAAK,MAAM,OAAO,IAAI,UAAU,EAAE,CAAC;YAClC,MAAM,cAAc,GAAG,MAAM,IAAI,CAAC,UAAU,CAAC,OAAO,CAAC,SAAS,CAAC,CAAC;YAChE,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,OAAO,EAAE,IAAI,EAAE,OAAO,EAAE,IAAI,EAAE,EAAE,GAAG,OAAO,EAAE,cAAc,EAAE,EAAE,CAAC;YAChE,CAAC;QACF,CAAC;QACD,OAAO,EAAE,IAAI,EAAE,eAAe,EAAE,iBAAiB,EAAE,CAAC;IACrD,CAAC;IAEM,KAAK,CAAC,YAAY;QACxB,MAAM,QAAQ,GAAG,MAAM,IAAI,CAAC,WAAW,EAAE,CAAC;QAC1C,kGAAkG;QAClG,2DAA2D;QAC3D,OAAO,OAAO,CAAC,GAAG,CACjB,QAAQ,CAAC,GAAG,CAAC,KAAK,EAAE,OAAO,EAAE,EAAE,CAAC,CAAC;YAChC,GAAG,OAAO;YACV,cAAc,EAAE,MAAM,IAAI,CAAC,UAAU,CAAC,OAAO,CAAC,SAAS,CAAC;SACxD,CAAC,CAAC,CACH,CAAC;IACH,CAAC;IAEO,KAAK,CAAC,WAAW;QACxB,gGAAgG;QAChG,8FAA8F;QAC9F,IAAI,CAAC,aAAa,KAAK,IAAI,CAAC,OAAO,CAAC,gBAAgB,EAAE,CAAC;QACvD,OAAO,IAAI,CAAC,aAAa,CAAC;IAC3B,CAAC;IAEO,KAAK,CAAC,UAAU,CAAC,SAAiB;QACzC,8FAA8F;QAC9F,mFAAmF;QACnF,IAAI,OAAO,GAAG,IAAI,CAAC,YAAY,CAAC,GAAG,CAAC,SAAS,CAAC,CAAC;QAC/C,IAAI,OAAO,KAAK,SAAS,EAAE,CAAC;YAC3B,OAAO,GAAG,IAAI,CAAC,OAAO,CAAC,qBAAqB,CAAC,SAAS,CAAC,CAAC;YACxD,IAAI,CAAC,YAAY,CAAC,GAAG,CAAC,SAAS,EAAE,OAAO,CAAC,CAAC;QAC3C,CAAC;QACD,OAAO,OAAO,CAAC;IAChB,CAAC;CACD;AAjED,gDAiEC;AAED;;GAEG;AACH,SAAgB,wBAAwB,CACvC,KAAkC;IAElC,OAAO,IAAI,kBAAkB,CAAC,IAAA,wDAA4B,EAAC,KAAK,CAAC,CAAC,CAAC;AACpE,CAAC;AAJD,4DAIC","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 {\n\tcreateOdspFileVersionFetcher,\n\ttype OdspFileVersionFetcherProps,\n} from \"./odspFileVersionFetcher.js\";\n\n/**\n * A single ODSP file version, as listed by the file's version history.\n */\nexport interface OdspFileVersionRef {\n\t/**\n\t * The version's label (e.g. `\"42.0\"`), used to address the version when fetching it.\n\t */\n\treadonly versionId: string;\n\t/**\n\t * Last-modified timestamp of this version, ISO-8601.\n\t */\n\treadonly lastModifiedDateTime: string;\n}\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 * There is intentionally no `targetIsLive` case: when the target is at/after the newest recoverable\n * version, the greatest version with `seq <= target` IS that newest version, so it is a normal\n * `found`. A consumer may separately choose to load the live file when the target is near the head.\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/** No recoverable version has `sequenceNumber <= target` (target predates retained history). */\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 * Provides a file's versions and resolves each version's Fluid sequence number. Injected into\n * the version manager so the selection logic does not depend on how versions are fetched.\n */\nexport interface IOdspFileVersionFetcher {\n\t/**\n\t * Enumerate the file's versions, newest-first.\n\t */\n\tlistFileVersions(): Promise<OdspFileVersionRef[]>;\n\t/**\n\t * Resolve a single version's Fluid sequence number. Throws on failure rather than returning a\n\t * wrong value.\n\t */\n\tresolveSequenceNumber(versionId: string): Promise<number>;\n}\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\tfindBaseForSeq(target: number): Promise<BaseForSeq>;\n}\n\n/**\n * Default {@link IOdspVersionManager}. Caches the version list and resolved sequence numbers. 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 */\nexport class OdspVersionManager implements IOdspVersionManager {\n\tprivate versionsCache: Promise<OdspFileVersionRef[]> | undefined;\n\tprivate readonly seqByVersion = new Map<string, Promise<number>>();\n\n\tpublic constructor(private readonly fetcher: IOdspFileVersionFetcher) {}\n\n\tpublic refresh(): void {\n\t\tthis.versionsCache = undefined;\n\t\tthis.seqByVersion.clear();\n\t}\n\n\tpublic async findBaseForSeq(target: number): Promise<BaseForSeq> {\n\t\t// Recoverable base candidates = every version except the tip (index 0 ≈ the live document).\n\t\tconst versions = await this.getVersions();\n\t\tconst candidates = versions.slice(1);\n\n\t\t// Versions are listed newest-first, and version order is expected to track sequence number, so\n\t\t// the first candidate whose seq is at or before the target is taken as the closest base. Because\n\t\t// any base at or before the target replays forward to the same state, this early stop is an\n\t\t// optimization, not a correctness requirement: if version order and sequence order ever diverge,\n\t\t// a base that is valid but not strictly the closest may be chosen.\n\t\t// Scanning newest-first also yields the newest of versions sharing a sequence number (dedup).\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\treturn { kind: \"found\", base: { ...version, sequenceNumber } };\n\t\t\t}\n\t\t}\n\t\treturn { kind: \"noBaseVersion\", oldestResolvedSeq };\n\t}\n\n\tpublic async listVersions(): Promise<ResolvedVersion[]> {\n\t\tconst versions = await this.getVersions();\n\t\t// Resolution order does not matter here, 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) => ({\n\t\t\t\t...version,\n\t\t\t\tsequenceNumber: await this.resolveSeq(version.versionId),\n\t\t\t})),\n\t\t);\n\t}\n\n\tprivate async getVersions(): Promise<OdspFileVersionRef[]> {\n\t\t// Cache the pending promise, not the awaited value, so concurrent callers share one fetch and a\n\t\t// refresh() that runs while the fetch is in flight is not overwritten when the fetch settles.\n\t\tthis.versionsCache ??= this.fetcher.listFileVersions();\n\t\treturn this.versionsCache;\n\t}\n\n\tprivate async resolveSeq(versionId: string): Promise<number> {\n\t\t// Cache the pending promise (a version's sequence number never changes) so concurrent callers\n\t\t// coalesce and a refresh() is not clobbered by a fetch that was already in flight.\n\t\tlet pending = this.seqByVersion.get(versionId);\n\t\tif (pending === undefined) {\n\t\t\tpending = this.fetcher.resolveSequenceNumber(versionId);\n\t\t\tthis.seqByVersion.set(versionId, pending);\n\t\t}\n\t\treturn pending;\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":"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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"]}
|
package/dist/packageVersion.d.ts
CHANGED
|
@@ -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 = "2.
|
|
8
|
+
export declare const pkgVersion = "2.115.0";
|
|
9
9
|
//# sourceMappingURL=packageVersion.d.ts.map
|
package/dist/packageVersion.js
CHANGED
|
@@ -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 = "2.
|
|
11
|
+
exports.pkgVersion = "2.115.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,SAAS,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 = \"2.
|
|
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,SAAS,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 = \"2.115.0\";\n"]}
|
|
@@ -19,6 +19,11 @@ import type { IClient, IDocumentDeltaConnection, IDocumentDeltaStorageService, I
|
|
|
19
19
|
* still catches up from the snapshot's sequence number through delta storage, which is exactly the
|
|
20
20
|
* bounded replay we want. As a result no live delta-stream connection is ever established.
|
|
21
21
|
*
|
|
22
|
+
* Op availability is enforced by the delta storage stack itself: it validates that fetched batches
|
|
23
|
+
* are contiguous from the requested start, keeps requesting until the bounded range is fully
|
|
24
|
+
* delivered, and fails the fetch if the ops never materialize. So a stream that completes has
|
|
25
|
+
* necessarily served the whole bridge, and no additional checks are needed here.
|
|
26
|
+
*
|
|
22
27
|
* @internal
|
|
23
28
|
*/
|
|
24
29
|
export declare class OdspPointInTimeDocumentService extends TypedEventEmitter<IDocumentServiceEvents> implements IDocumentService {
|
|
@@ -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,KAAK,EACX,OAAO,EACP,wBAAwB,EACxB,4BAA4B,EAC5B,gBAAgB,EAChB,sBAAsB,EACtB,wBAAwB,EACxB,uBAAuB,EACvB,YAAY,EACZ,MAAM,6CAA6C,CAAC;AAErD
|
|
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,KAAK,EACX,OAAO,EACP,wBAAwB,EACxB,4BAA4B,EAC5B,gBAAgB,EAChB,sBAAsB,EACtB,wBAAwB,EACxB,uBAAuB,EACvB,YAAY,EACZ,MAAM,6CAA6C,CAAC;AAErD;;;;;;;;;;;;;;;;;;;;;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;IAIpD,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"}
|
|
@@ -21,6 +21,11 @@ const client_utils_1 = require("@fluid-internal/client-utils");
|
|
|
21
21
|
* still catches up from the snapshot's sequence number through delta storage, which is exactly the
|
|
22
22
|
* bounded replay we want. As a result no live delta-stream connection is ever established.
|
|
23
23
|
*
|
|
24
|
+
* Op availability is enforced by the delta storage stack itself: it validates that fetched batches
|
|
25
|
+
* are contiguous from the requested start, keeps requesting until the bounded range is fully
|
|
26
|
+
* delivered, and fails the fetch if the ops never materialize. So a stream that completes has
|
|
27
|
+
* necessarily served the whole bridge, and no additional checks are needed here.
|
|
28
|
+
*
|
|
24
29
|
* @internal
|
|
25
30
|
*/
|
|
26
31
|
class OdspPointInTimeDocumentService extends client_utils_1.TypedEventEmitter {
|
|
@@ -54,9 +59,7 @@ class OdspPointInTimeDocumentService extends client_utils_1.TypedEventEmitter {
|
|
|
54
59
|
// The exclusive upper bound needed to include the target op itself.
|
|
55
60
|
const boundedTo = this.targetSequenceNumber + 1;
|
|
56
61
|
return {
|
|
57
|
-
fetchMessages: (from, to, abortSignal, cachedOnly, fetchReason) =>
|
|
58
|
-
return liveDeltaStorage.fetchMessages(from, to === undefined ? boundedTo : Math.min(to, boundedTo), abortSignal, cachedOnly, fetchReason);
|
|
59
|
-
},
|
|
62
|
+
fetchMessages: (from, to, abortSignal, cachedOnly, fetchReason) => liveDeltaStorage.fetchMessages(from, to === undefined ? boundedTo : Math.min(to, boundedTo), abortSignal, cachedOnly, fetchReason),
|
|
60
63
|
};
|
|
61
64
|
}
|
|
62
65
|
async connectToDeltaStream(_client) {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"odspPointInTimeDocumentService.js","sourceRoot":"","sources":["../../src/pointInTimeDriver/odspPointInTimeDocumentService.ts"],"names":[],"mappings":";AAAA;;;GAGG;;;AAEH,+DAAiE;AAYjE
|
|
1
|
+
{"version":3,"file":"odspPointInTimeDocumentService.js","sourceRoot":"","sources":["../../src/pointInTimeDriver/odspPointInTimeDocumentService.ts"],"names":[],"mappings":";AAAA;;;GAGG;;;AAEH,+DAAiE;AAYjE;;;;;;;;;;;;;;;;;;;;;GAqBG;AACH,MAAa,8BACZ,SAAQ,gCAAyC;IAGjD,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;QAM9C,+FAA+F;QAC/F,qEAAqE;QACrD,aAAQ,GAA6B,EAAE,WAAW,EAAE,IAAI,EAAE,CAAC;QAqC1D,0BAAqB,GAAG,CAAC,QAAgC,EAAQ,EAAE;YACnF,IAAI,CAAC,IAAI,CAAC,gBAAgB,EAAE,QAAQ,CAAC,CAAC;QACvC,CAAC,CAAC;QA5CD,IAAI,CAAC,mBAAmB,CAAC,EAAE,CAAC,gBAAgB,EAAE,IAAI,CAAC,qBAAqB,CAAC,CAAC;IAC3E,CAAC;IAMM,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,OAAO,IAAI,CAAC,0BAA0B,CAAC,gBAAgB,EAAE,CAAC;IAC3D,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;CAKD;AA1DD,wEA0DC","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 type {\n\tIClient,\n\tIDocumentDeltaConnection,\n\tIDocumentDeltaStorageService,\n\tIDocumentService,\n\tIDocumentServiceEvents,\n\tIDocumentServicePolicies,\n\tIDocumentStorageService,\n\tIResolvedUrl,\n} from \"@fluidframework/driver-definitions/internal\";\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\treturn this.recoverableDocumentService.connectToStorage();\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"]}
|
|
@@ -3,47 +3,39 @@
|
|
|
3
3
|
* Licensed under the MIT License.
|
|
4
4
|
*/
|
|
5
5
|
import type { ITelemetryBaseLogger } from "@fluidframework/core-interfaces";
|
|
6
|
-
import type { IDocumentService, IPersistedCache, IResolvedUrl } from "@fluidframework/driver-definitions/internal";
|
|
6
|
+
import type { IDocumentService, IDocumentServiceFactory, IPersistedCache, IResolvedUrl } from "@fluidframework/driver-definitions/internal";
|
|
7
7
|
import type { HostStoragePolicy, OdspResourceTokenFetchOptions, TokenFetcher } from "@fluidframework/odsp-driver-definitions/internal";
|
|
8
|
-
import { OdspDocumentServiceFactoryCore } from "../odspDocumentServiceFactoryCore.js";
|
|
9
8
|
/**
|
|
10
|
-
* ODSP document service factory that
|
|
11
|
-
* loading.
|
|
9
|
+
* An ODSP document service factory that supports point-in-time (sequence-number-based) loading.
|
|
12
10
|
*
|
|
13
11
|
* @remarks
|
|
14
|
-
*
|
|
15
|
-
*
|
|
16
|
-
* presence of {@link OdspPointInTimeDocumentServiceFactory.createPointInTimeDocumentService}, so
|
|
17
|
-
* hosts that want to load a container to a target sequence number must construct this factory
|
|
18
|
-
* (rather than the legacy `OdspDocumentServiceFactory`) and pass it to the loader.
|
|
12
|
+
* The loader detects this capability structurally, so hosts can pass this factory directly to
|
|
13
|
+
* `loadContainerToSequenceNumber`.
|
|
19
14
|
*
|
|
20
|
-
* @
|
|
15
|
+
* @legacy @alpha
|
|
21
16
|
*/
|
|
22
|
-
export
|
|
17
|
+
export interface IPointInTimeDocumentServiceFactory extends IDocumentServiceFactory {
|
|
23
18
|
/**
|
|
24
|
-
*
|
|
25
|
-
*/
|
|
26
|
-
private readonly getStorageTokenForVersions;
|
|
27
|
-
constructor(getStorageToken: TokenFetcher<OdspResourceTokenFetchOptions>, getWebsocketToken: TokenFetcher<OdspResourceTokenFetchOptions> | undefined, persistedCache?: IPersistedCache, hostPolicy?: HostStoragePolicy);
|
|
28
|
-
/**
|
|
29
|
-
* Creates a document service that reads its snapshot from the closest file version at or before
|
|
30
|
-
* the target and its deltas from the live document, materializing a requested sequence number
|
|
31
|
-
* through replay.
|
|
32
|
-
*/
|
|
33
|
-
createPointInTimeDocumentService(resolvedUrl: IResolvedUrl, targetSequenceNumber: number, logger?: ITelemetryBaseLogger, clientIsSummarizer?: boolean): Promise<IDocumentService>;
|
|
34
|
-
/**
|
|
35
|
-
* Builds an IOdspVersionManager for the given file, which enumerates the file's stored
|
|
36
|
-
* versions and resolves the closest version at or before a target sequence number.
|
|
19
|
+
* Creates a document service that materializes the document at the requested sequence number.
|
|
37
20
|
*
|
|
38
|
-
* @
|
|
39
|
-
*
|
|
40
|
-
*
|
|
41
|
-
*
|
|
42
|
-
*
|
|
43
|
-
* {@link OdspPointInTimeDocumentServiceFactory.createPointInTimeDocumentService} to pick the base
|
|
44
|
-
* snapshot for point-in-time loading.
|
|
21
|
+
* @param resolvedUrl - The resolved ODSP document URL.
|
|
22
|
+
* @param targetSequenceNumber - The sequence number at which to materialize the document.
|
|
23
|
+
* @param logger - Optional telemetry logger.
|
|
24
|
+
* @param clientIsSummarizer - Whether the requesting client is a summarizer.
|
|
25
|
+
* @returns A read-only document service materialized at the requested sequence number.
|
|
45
26
|
*/
|
|
46
|
-
|
|
47
|
-
private resolveFileVersion;
|
|
27
|
+
createPointInTimeDocumentService(resolvedUrl: IResolvedUrl, targetSequenceNumber: number, logger?: ITelemetryBaseLogger, clientIsSummarizer?: boolean): Promise<IDocumentService>;
|
|
48
28
|
}
|
|
29
|
+
/**
|
|
30
|
+
* Creates an ODSP document service factory that supports point-in-time loading.
|
|
31
|
+
*
|
|
32
|
+
* @param getStorageToken - Fetches storage access tokens.
|
|
33
|
+
* @param getWebsocketToken - Fetches websocket access tokens, or `undefined` when unavailable.
|
|
34
|
+
* @param persistedCache - Optional persisted ODSP cache.
|
|
35
|
+
* @param hostPolicy - Optional host storage policy.
|
|
36
|
+
* @returns An ODSP document service factory with point-in-time loading capability.
|
|
37
|
+
*
|
|
38
|
+
* @legacy @alpha
|
|
39
|
+
*/
|
|
40
|
+
export declare function getOdspPointInTimeDocumentServiceFactory(getStorageToken: TokenFetcher<OdspResourceTokenFetchOptions>, getWebsocketToken: TokenFetcher<OdspResourceTokenFetchOptions> | undefined, persistedCache?: IPersistedCache, hostPolicy?: HostStoragePolicy): IPointInTimeDocumentServiceFactory;
|
|
49
41
|
//# sourceMappingURL=odspPointInTimeDocumentServiceFactory.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"odspPointInTimeDocumentServiceFactory.d.ts","sourceRoot":"","sources":["../../src/pointInTimeDriver/odspPointInTimeDocumentServiceFactory.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,OAAO,KAAK,EAAE,oBAAoB,EAAE,MAAM,iCAAiC,CAAC;AAC5E,OAAO,KAAK,EACX,gBAAgB,EAChB,eAAe,EACf,YAAY,EACZ,MAAM,6CAA6C,CAAC;AACrD,OAAO,KAAK,EACX,iBAAiB,
|
|
1
|
+
{"version":3,"file":"odspPointInTimeDocumentServiceFactory.d.ts","sourceRoot":"","sources":["../../src/pointInTimeDriver/odspPointInTimeDocumentServiceFactory.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,OAAO,KAAK,EAAE,oBAAoB,EAAE,MAAM,iCAAiC,CAAC;AAC5E,OAAO,KAAK,EACX,gBAAgB,EAChB,uBAAuB,EACvB,eAAe,EACf,YAAY,EACZ,MAAM,6CAA6C,CAAC;AACrD,OAAO,KAAK,EACX,iBAAiB,EAGjB,6BAA6B,EAC7B,YAAY,EACZ,MAAM,kDAAkD,CAAC;AAuB1D;;;;;;;;GAQG;AACH,MAAM,WAAW,kCAAmC,SAAQ,uBAAuB;IAClF;;;;;;;;OAQG;IACH,gCAAgC,CAC/B,WAAW,EAAE,YAAY,EACzB,oBAAoB,EAAE,MAAM,EAC5B,MAAM,CAAC,EAAE,oBAAoB,EAC7B,kBAAkB,CAAC,EAAE,OAAO,GAC1B,OAAO,CAAC,gBAAgB,CAAC,CAAC;CAC7B;AA0KD;;;;;;;;;;GAUG;AACH,wBAAgB,wCAAwC,CACvD,eAAe,EAAE,YAAY,CAAC,6BAA6B,CAAC,EAC5D,iBAAiB,EAAE,YAAY,CAAC,6BAA6B,CAAC,GAAG,SAAS,EAC1E,cAAc,CAAC,EAAE,eAAe,EAChC,UAAU,CAAC,EAAE,iBAAiB,GAC5B,kCAAkC,CAOpC"}
|