diff --git a/c-sharp-tests/Projects/VersificationServiceTests.cs b/c-sharp-tests/Projects/ParatextProjectDataProviderVersificationTests.cs similarity index 62% rename from c-sharp-tests/Projects/VersificationServiceTests.cs rename to c-sharp-tests/Projects/ParatextProjectDataProviderVersificationTests.cs index ba6ba161e8b..2494ee53a23 100644 --- a/c-sharp-tests/Projects/VersificationServiceTests.cs +++ b/c-sharp-tests/Projects/ParatextProjectDataProviderVersificationTests.cs @@ -5,42 +5,42 @@ namespace TestParanextDataProvider.Projects { /// - /// Unit tests for . + /// Unit tests for the versification methods + /// — i.e. the methods registered under the platformScripture.Versification + /// projectInterface: /// - /// - /// The service exposes three RPC functions that delegate to libpalaso's - /// ScrVers via ScrText.Settings.Versification: - /// /// - /// LookupFinalVerseNumber — passthrough. - /// LookupFinalChapter — passthrough. - /// LookupFinalVerseNumbersInBook — passthrough plus - /// bookkeeping: returns int[lastChapter + 1] with index 0 unused - /// and indices 1..lastChapter populated. The off-by-one boundary - /// is the most worth testing. + /// LookupFinalVerseNumber(bookNum, chapterNum) — passthrough to + /// ScrText.Settings.Versification.GetLastVerse. + /// LookupFinalChapter(bookNum) — passthrough to + /// ScrText.Settings.Versification.GetLastChapter. + /// LookupFinalVerseNumbersInBook(bookNum) — passthrough plus bookkeeping: + /// returns int[lastChapter + 1] with index 0 a filler 0 and indices + /// 1..lastChapter populated for ergonomic result[chapterNum] access. + /// The off-by-one boundary is the most worth testing. /// /// /// - /// Setup follows the existing pattern: a - /// is created and registered via - /// ParatextProjects.FakeAddProject, then resolved through the - /// production LocalParatextProjects.GetParatextProject static - /// lookup that uses internally. Each - /// test compares the service result against the underlying versification - /// lookups directly so the assertions remain valid regardless of whether - /// the project's default versification is English, Original, etc. + /// Setup follows the same pattern used by : + /// a is created and registered via + /// ParatextProjects.FakeAddProject, then we construct a + /// that subclasses the real PDP and gives + /// us direct in-process access to the registered methods. Each test compares the PDP + /// result against the underlying versification lookups directly so the assertions stay + /// valid regardless of which versification scheme the dummy project happens to default to. /// /// [ExcludeFromCodeCoverage] [TestFixture] - internal class VersificationServiceTests : PapiTestBase + internal class ParatextProjectDataProviderVersificationTests : PapiTestBase { + private const string PdpName = "versificationTestProject"; private const int GenesisBookNum = 1; private const int PhilemonBookNum = 57; private ScrText _scrText = null!; private ProjectDetails _projectDetails = null!; - private VersificationService _service = null!; + private DummyParatextProjectDataProvider _provider = null!; [SetUp] public override async Task TestSetupAsync() @@ -51,7 +51,12 @@ public override async Task TestSetupAsync() _projectDetails = CreateProjectDetails(_scrText); ParatextProjects.FakeAddProject(_projectDetails, _scrText); - _service = new VersificationService(Client); + _provider = new DummyParatextProjectDataProvider( + PdpName, + Client, + _projectDetails, + ParatextProjects + ); } [TearDown] @@ -68,7 +73,8 @@ public void TearDown() [Test] [Description( "For a multi-chapter book (Genesis, 50 chapters), the returned array length " - + "must be lastChapter + 1 — index 0 reserved as 'unused'." + + "must be lastChapter + 1 — index 0 reserved as a filler so consumers can " + + "index by 1-based chapter number directly." )] public void LookupFinalVerseNumbersInBook_Genesis_ReturnsArrayOfLengthLastChapterPlusOne() { @@ -76,16 +82,13 @@ public void LookupFinalVerseNumbersInBook_Genesis_ReturnsArrayOfLengthLastChapte GenesisBookNum ); - var result = _service.LookupFinalVerseNumbersInBook( - _projectDetails.Metadata.Id, - GenesisBookNum - ); + var result = _provider.LookupFinalVerseNumbersInBook(GenesisBookNum); Assert.That(result, Is.Not.Null); Assert.That( result.Length, Is.EqualTo(expectedLastChapter + 1), - "array length must be lastChapter + 1 (index 0 reserved as unused)" + "array length must be lastChapter + 1 (index 0 reserved as filler)" ); } @@ -95,10 +98,7 @@ public void LookupFinalVerseNumbersInBook_Genesis_ReturnsArrayOfLengthLastChapte )] public void LookupFinalVerseNumbersInBook_Genesis_IndexZeroIsUnusedAndDefaults() { - var result = _service.LookupFinalVerseNumbersInBook( - _projectDetails.Metadata.Id, - GenesisBookNum - ); + var result = _provider.LookupFinalVerseNumbersInBook(GenesisBookNum); Assert.That(result[0], Is.EqualTo(0), "index 0 is unused — must be default(int)"); } @@ -112,10 +112,7 @@ public void LookupFinalVerseNumbersInBook_Genesis_IndexOneMatchesGetLastVerseOfC { int expected = _scrText.Settings.Versification.GetLastVerse(GenesisBookNum, 1); - var result = _service.LookupFinalVerseNumbersInBook( - _projectDetails.Metadata.Id, - GenesisBookNum - ); + var result = _provider.LookupFinalVerseNumbersInBook(GenesisBookNum); Assert.That( result[1], @@ -137,10 +134,7 @@ public void LookupFinalVerseNumbersInBook_Genesis_LastIndexMatchesGetLastVerseOf lastChapter ); - var result = _service.LookupFinalVerseNumbersInBook( - _projectDetails.Metadata.Id, - GenesisBookNum - ); + var result = _provider.LookupFinalVerseNumbersInBook(GenesisBookNum); Assert.That( result[lastChapter], @@ -160,10 +154,7 @@ public void LookupFinalVerseNumbersInBook_Genesis_AllChaptersMatchVersificationL var versification = _scrText.Settings.Versification; int lastChapter = versification.GetLastChapter(GenesisBookNum); - var result = _service.LookupFinalVerseNumbersInBook( - _projectDetails.Metadata.Id, - GenesisBookNum - ); + var result = _provider.LookupFinalVerseNumbersInBook(GenesisBookNum); for (int chapter = 1; chapter <= lastChapter; chapter++) { @@ -178,7 +169,7 @@ public void LookupFinalVerseNumbersInBook_Genesis_AllChaptersMatchVersificationL [Test] [Description( "For a single-chapter book (Philemon), the returned array length must be 2 " - + "(index 0 unused, index 1 = last verse of chapter 1)." + + "(index 0 filler, index 1 = last verse of chapter 1)." )] public void LookupFinalVerseNumbersInBook_Philemon_ReturnsArrayOfLengthTwo() { @@ -193,13 +184,10 @@ public void LookupFinalVerseNumbersInBook_Philemon_ReturnsArrayOfLengthTwo() 1 ); - var result = _service.LookupFinalVerseNumbersInBook( - _projectDetails.Metadata.Id, - PhilemonBookNum - ); + var result = _provider.LookupFinalVerseNumbersInBook(PhilemonBookNum); Assert.That(result.Length, Is.EqualTo(2), "single-chapter book -> length 2"); - Assert.That(result[0], Is.EqualTo(0), "index 0 is unused"); + Assert.That(result[0], Is.EqualTo(0), "index 0 is filler"); Assert.That( result[1], Is.EqualTo(expectedLastVerse), @@ -211,8 +199,8 @@ public void LookupFinalVerseNumbersInBook_Philemon_ReturnsArrayOfLengthTwo() // LookupFinalVerseNumber & LookupFinalChapter — passthrough wiring. // // These methods delegate directly to libpalaso. The tests below are - // sanity checks confirming the wiring (project lookup -> versification - // -> result) is intact, not re-tests of libpalaso. + // sanity checks confirming the wiring (PDP -> project lookup -> + // versification -> result) is intact, not re-tests of libpalaso. // ===================================================================== [Test] @@ -224,11 +212,7 @@ public void LookupFinalVerseNumber_Genesis1_MatchesUnderlyingVersification() { int expected = _scrText.Settings.Versification.GetLastVerse(GenesisBookNum, 1); - int actual = _service.LookupFinalVerseNumber( - _projectDetails.Metadata.Id, - GenesisBookNum, - 1 - ); + int actual = _provider.LookupFinalVerseNumber(GenesisBookNum, 1); Assert.That(actual, Is.EqualTo(expected)); } @@ -242,35 +226,9 @@ public void LookupFinalChapter_Genesis_MatchesUnderlyingVersification() { int expected = _scrText.Settings.Versification.GetLastChapter(GenesisBookNum); - int actual = _service.LookupFinalChapter(_projectDetails.Metadata.Id, GenesisBookNum); + int actual = _provider.LookupFinalChapter(GenesisBookNum); Assert.That(actual, Is.EqualTo(expected)); } - - // ===================================================================== - // Unknown projectId — error propagation. - // - // LocalParatextProjects.GetParatextProject delegates to - // ScrTextCollection.GetById which throws ProjectNotFoundException for - // an id with no matching project. The service does not catch it, so it - // must propagate to the caller. (See ParatextProjectDataProviderFactoryTests - // for the same pattern at the factory layer.) - // ===================================================================== - - [Test] - [Description( - "An unknown projectId propagates ProjectNotFoundException from " - + "LocalParatextProjects.GetParatextProject." - )] - public void LookupFinalVerseNumbersInBook_UnknownProjectId_ThrowsProjectNotFoundException() - { - // "00" is the canonical 'no such project' id used in the existing - // ParatextProjectDataProviderFactoryTests fixture. - const string unknownProjectId = "00"; - - Assert.Throws( - () => _service.LookupFinalVerseNumbersInBook(unknownProjectId, GenesisBookNum) - ); - } } } diff --git a/c-sharp/Program.cs b/c-sharp/Program.cs index 89b7a6b3638..fa42023642c 100644 --- a/c-sharp/Program.cs +++ b/c-sharp/Program.cs @@ -80,7 +80,6 @@ public static async Task Main() var dblResources = new DblResourcesDataProvider(papi); var paratextRegistrationService = new ParatextRegistrationService(papi); var checklistNetworkObject = new ChecklistNetworkObject(papi); - var versificationService = new VersificationService(papi); var manageBooksService = new ManageBooksService( papi, paratextProjects, @@ -93,7 +92,6 @@ await Task.WhenAll( dblResources.RegisterDataProviderAsync(), paratextRegistrationService.InitializeAsync(), checklistNetworkObject.InitializeAsync(), - versificationService.InitializeAsync(), manageBooksService.RegisterNetworkObjectAsync() ); diff --git a/c-sharp/Projects/LocalParatextProjects.cs b/c-sharp/Projects/LocalParatextProjects.cs index 8f9276eddd4..90f6e23bd88 100644 --- a/c-sharp/Projects/LocalParatextProjects.cs +++ b/c-sharp/Projects/LocalParatextProjects.cs @@ -44,6 +44,7 @@ internal class LocalParatextProjects ProjectInterfaces.USX_VERSE, ProjectInterfaces.PLAIN_TEXT_VERSE, ProjectInterfaces.MARKER_NAMES, + ProjectInterfaces.VERSIFICATION, ]; public LocalParatextProjects() diff --git a/c-sharp/Projects/ParatextProjectDataProvider.cs b/c-sharp/Projects/ParatextProjectDataProvider.cs index 88e37140ff1..d2c9d3f29d2 100644 --- a/c-sharp/Projects/ParatextProjectDataProvider.cs +++ b/c-sharp/Projects/ParatextProjectDataProvider.cs @@ -110,6 +110,10 @@ LocalParatextProjects paratextProjects retVal.Add(("getMarkerNames", GetMarkerNames)); + retVal.Add(("lookupFinalVerseNumber", LookupFinalVerseNumber)); + retVal.Add(("lookupFinalChapter", LookupFinalChapter)); + retVal.Add(("lookupFinalVerseNumbersInBook", LookupFinalVerseNumbersInBook)); + return retVal; } @@ -1356,6 +1360,48 @@ public string[] GetMarkerNames(int bookNum) #endregion + #region Versification (platformScripture.Versification) + + /// + /// Returns the final verse number in the specified book and chapter using the project's + /// versification. Each call reads ScrText.Settings.Versification fresh, so results + /// reflect any in-session changes to the project's versification setting. + /// + public int LookupFinalVerseNumber(int bookNum, int chapterNum) + { + var scrText = LocalParatextProjects.GetParatextProject(ProjectDetails.Metadata.Id); + return scrText.Settings.Versification.GetLastVerse(bookNum, chapterNum); + } + + /// + /// Returns the final chapter number in the specified book using the project's versification. + /// + public int LookupFinalChapter(int bookNum) + { + var scrText = LocalParatextProjects.GetParatextProject(ProjectDetails.Metadata.Id); + return scrText.Settings.Versification.GetLastChapter(bookNum); + } + + /// + /// Returns the final verse number for each chapter in the specified book using the project's + /// versification. Index n is the last verse number in chapter n (1-based); + /// index 0 is a filler 0 so callers can use result[chapterNum] without + /// off-by-one. The returned array has length lastChapter + 1. Useful for pre-fetching + /// a whole book in one round trip. + /// + public int[] LookupFinalVerseNumbersInBook(int bookNum) + { + var scrText = LocalParatextProjects.GetParatextProject(ProjectDetails.Metadata.Id); + var versification = scrText.Settings.Versification; + int lastChapter = versification.GetLastChapter(bookNum); + int[] result = new int[lastChapter + 1]; + for (int chapter = 1; chapter <= lastChapter; chapter++) + result[chapter] = versification.GetLastVerse(bookNum, chapter); + return result; + } + + #endregion + #region USX public string GetBookUsx(VerseRef verseRef) diff --git a/c-sharp/Projects/ProjectInterfaces.cs b/c-sharp/Projects/ProjectInterfaces.cs index a4151b12f2d..e5638f98d89 100644 --- a/c-sharp/Projects/ProjectInterfaces.cs +++ b/c-sharp/Projects/ProjectInterfaces.cs @@ -19,4 +19,5 @@ public static class ProjectInterfaces public const string PLAIN_TEXT_VERSE = "platformScripture.PlainText_Verse"; public const string LEGACY_COMMENT = "legacyCommentManager.comments"; public const string MARKER_NAMES = "platformScripture.MarkerNames"; + public const string VERSIFICATION = "platformScripture.Versification"; } diff --git a/c-sharp/Projects/VersificationService.cs b/c-sharp/Projects/VersificationService.cs deleted file mode 100644 index f45657e502c..00000000000 --- a/c-sharp/Projects/VersificationService.cs +++ /dev/null @@ -1,72 +0,0 @@ -using Paranext.DataProvider.NetworkObjects; - -namespace Paranext.DataProvider.Projects; - -/// -/// Network object exposing each project's versification lookups (last chapter per book, -/// last verse per chapter). Read-only; delegates to libpalaso's ScrVers via -/// ScrText.Settings.Versification. -/// -internal class VersificationService : NetworkObject -{ - private const string NetworkObjectName = "platformScripture.versificationService"; - - public VersificationService(PapiClient papiClient) - : base(papiClient) { } - - public async Task InitializeAsync() - { - List<(string functionName, Delegate function)> functions = - [ - ("lookupFinalVerseNumber", LookupFinalVerseNumber), - ("lookupFinalChapter", LookupFinalChapter), - ("lookupFinalVerseNumbersInBook", LookupFinalVerseNumbersInBook), - ]; - - await RegisterNetworkObjectAsync( - NetworkObjectName, - functions, - new NetworkObjectCreatedDetails - { - Id = NetworkObjectName, - ObjectType = NetworkObjectType.OBJECT, - FunctionNames = [.. functions.Select(f => f.functionName)], - } - ); - } - - /// - /// Returns the final verse number in the specified book and chapter using the project's - /// versification. - /// - public int LookupFinalVerseNumber(string projectId, int bookNum, int chapterNum) - { - var scrText = LocalParatextProjects.GetParatextProject(projectId); - return scrText.Settings.Versification.GetLastVerse(bookNum, chapterNum); - } - - /// - /// Returns the final chapter number in the specified book using the project's versification. - /// - public int LookupFinalChapter(string projectId, int bookNum) - { - var scrText = LocalParatextProjects.GetParatextProject(projectId); - return scrText.Settings.Versification.GetLastChapter(bookNum); - } - - /// - /// Returns the final verse number for each chapter in the specified book using the project's - /// versification. Index n is the last verse number in chapter n (1-based); - /// index 0 is unused. Useful for pre-fetching a whole book in one round trip. - /// - public int[] LookupFinalVerseNumbersInBook(string projectId, int bookNum) - { - var scrText = LocalParatextProjects.GetParatextProject(projectId); - var versification = scrText.Settings.Versification; - int lastChapter = versification.GetLastChapter(bookNum); - int[] result = new int[lastChapter + 1]; - for (int chapter = 1; chapter <= lastChapter; chapter++) - result[chapter] = versification.GetLastVerse(bookNum, chapter); - return result; - } -} diff --git a/extensions/src/platform-scripture-editor/src/platform-scripture-editor.web-view.tsx b/extensions/src/platform-scripture-editor/src/platform-scripture-editor.web-view.tsx index aa4f34d23b6..4ccaabe05bb 100644 --- a/extensions/src/platform-scripture-editor/src/platform-scripture-editor.web-view.tsx +++ b/extensions/src/platform-scripture-editor/src/platform-scripture-editor.web-view.tsx @@ -26,7 +26,6 @@ import { useProjectSetting, useRecentScriptureRefs, } from '@papi/frontend/react'; -import type { IVersificationService } from 'platform-scripture'; import { Canon, SerializedVerseRef } from '@sillsdev/scripture'; import type { CommandHandlers, CommandNames } from 'papi-shared-types'; import { @@ -349,21 +348,18 @@ globalThis.webViewComponent = function PlatformScriptureEditor({ // verse selection. When the book changes we refetch; for books other than the current one we do // not offer verse selection (the picker falls back to chapter-level submission). const currentBookNum = useMemo(() => Canon.bookIdToNumber(scrRef.book), [scrRef.book]); + const versificationPdp = useProjectDataProvider('platformScripture.Versification', projectId); const fetchLastVersesInCurrentBook = useCallback(async (): Promise => { - if (!projectId || currentBookNum <= 0) return undefined; + if (!versificationPdp || currentBookNum <= 0) return undefined; try { - const versificationService = await papi.networkObjects.get( - 'platformScripture.versificationService', - ); - if (!versificationService) return undefined; - return await versificationService.lookupFinalVerseNumbersInBook(projectId, currentBookNum); + return await versificationPdp.lookupFinalVerseNumbersInBook(currentBookNum); } catch (err) { logger.debug( `Failed to fetch verse counts for book ${currentBookNum}: ${getErrorMessage(err)}`, ); return undefined; } - }, [projectId, currentBookNum]); + }, [versificationPdp, currentBookNum]); const [lastVersesInCurrentBook] = usePromise(fetchLastVersesInCurrentBook, undefined); const getEndVerse = useCallback( (bookId: string, chapterNum: number): number => { diff --git a/extensions/src/platform-scripture/src/checklist.web-view.tsx b/extensions/src/platform-scripture/src/checklist.web-view.tsx index d2f9e4b7a7d..8ff1102ee64 100644 --- a/extensions/src/platform-scripture/src/checklist.web-view.tsx +++ b/extensions/src/platform-scripture/src/checklist.web-view.tsx @@ -1,6 +1,6 @@ import { WebViewProps } from '@papi/core'; import papi, { logger, network } from '@papi/frontend'; -import { useData, useLocalizedStrings } from '@papi/frontend/react'; +import { useData, useLocalizedStrings, useProjectDataProvider } from '@papi/frontend/react'; import { useEvent, ProjectSelector, @@ -24,7 +24,6 @@ import type { ChecklistRequest, ChecklistResultResponse, ChecklistScriptureRange, - IVersificationService, } from 'platform-scripture'; import { useCallback, useEffect, useMemo, useRef, useState } from 'react'; import { ChecklistTool, CHECKLIST_STRING_KEYS } from './components/checklist.component'; @@ -432,25 +431,23 @@ global.webViewComponent = function ChecklistWebView({ // ─── Versification lookups (Theme 6) ────────────────────────────────────── // - // Mirrors platform-scripture-editor.web-view.tsx:351-377. Uses VersificationService for - // current-book verse counts; other books would need their own fetch/cache (matches the - // scripture-editor's existing limitation). + // Mirrors the versification-PDP block in platform-scripture-editor.web-view.tsx. Uses the + // per-project Versification PDP for current-book verse counts; other books would need their own + // fetch/cache (matches the scripture-editor's existing limitation). const currentBookNum = useMemo(() => Canon.bookIdToNumber(liveScrRef.book), [liveScrRef.book]); + const versificationPdp = useProjectDataProvider('platformScripture.Versification', projectId); + const fetchLastVersesInCurrentBook = useCallback(async (): Promise => { - if (!projectId || currentBookNum <= 0) return undefined; + if (!versificationPdp || currentBookNum <= 0) return undefined; try { - const versificationService = await papi.networkObjects.get( - 'platformScripture.versificationService', - ); - if (!versificationService) return undefined; - return await versificationService.lookupFinalVerseNumbersInBook(projectId, currentBookNum); + return await versificationPdp.lookupFinalVerseNumbersInBook(currentBookNum); } catch (err) { - logger.debug(`ChecklistWebView: VersificationService unavailable: ${getErrorMessage(err)}`); + logger.debug(`ChecklistWebView: Versification PDP unavailable: ${getErrorMessage(err)}`); return undefined; } - }, [projectId, currentBookNum]); + }, [versificationPdp, currentBookNum]); const [lastVersesInCurrentBook] = usePromise(fetchLastVersesInCurrentBook, undefined); const getEndVerse = useCallback( @@ -462,10 +459,10 @@ global.webViewComponent = function ChecklistWebView({ ); // Last-chapter lookup derived from the same per-book array as getEndVerse. - // The verses array is 1-indexed (matches scripture-editor.web-view.tsx:374's - // `[chapterNum]` access pattern), so length - 1 yields the highest chapter number. - // Returns 0 for non-current books — computeRangeFromScope tolerates 0 by falling back - // to the documented 999 sentinel (FALLBACK_END_CHAPTER). + // The verses array is 1-indexed (matches the `[chapterNum]` access in getEndVerse above), so + // length - 1 yields the highest chapter number. Returns 0 for non-current books — + // computeRangeFromScope tolerates 0 by falling back to the documented 999 sentinel + // (FALLBACK_END_CHAPTER). const getLastChapter = useCallback( (bookId: string): number => { if (Canon.bookIdToNumber(bookId) !== currentBookNum) return 0; diff --git a/extensions/src/platform-scripture/src/types/platform-scripture.d.ts b/extensions/src/platform-scripture/src/types/platform-scripture.d.ts index 2fc3cb74f61..fc712fed8d5 100644 --- a/extensions/src/platform-scripture/src/types/platform-scripture.d.ts +++ b/extensions/src/platform-scripture/src/types/platform-scripture.d.ts @@ -802,29 +802,55 @@ declare module 'platform-scripture' { // #region Versification Types /** - * Read-only lookups for a project's versification — final chapter per book, final verse per - * chapter. Consumers (e.g. reference pickers) use these to constrain selection to valid - * references for a given project. This is a network object (not a project data provider): - * versification is fixed at project open and does not change at runtime, so there is no - * subscription semantics. + * Data types the versification projectInterface exposes via its base {@link IProjectDataProvider} + * — currently empty. The interface is auxiliary-only (no `get*` / `set*` / `subscribe*` data + * types). Consumers that need to react to versification changes should subscribe to the + * `'platformScripture.versification'` project setting on the same PDP via the base + * setting-subscription methods. + */ + export type VersificationProjectInterfaceDataTypes = Record; + + /** + * Project-scoped read-only versification lookups — final chapter per book, final verse per + * chapter. Consumers (e.g. reference pickers, range validators) use these to constrain selection + * to valid references for the project. * - * Obtain via - * `papi.networkObjects.get('platformScripture.versificationService')`. + * Each call reads the project's _current_ versification fresh, so results reflect any in-session + * changes to the `platformScripture.versification` project setting. Consumers that cache results + * across calls should invalidate their cache when that setting changes. + * + * Acquire via `papi.projectDataProviders.get('platformScripture.Versification', projectId)` + * (backend) or the `useProjectDataProvider('platformScripture.Versification', projectId)` React + * hook (frontend). */ - export type IVersificationService = { - /** - * Returns the final verse number in the specified book and chapter using the project's - * versification. - */ - lookupFinalVerseNumber(projectId: string, bookNum: number, chapterNum: number): Promise; - /** Returns the final chapter number in the specified book using the project's versification. */ - lookupFinalChapter(projectId: string, bookNum: number): Promise; - /** - * Returns an array where index `n` is the last verse number in chapter `n` (1-based). Index 0 - * is unused. Useful for pre-fetching all verse counts for a book in a single round trip. - */ - lookupFinalVerseNumbersInBook(projectId: string, bookNum: number): Promise; - }; + export type IVersificationProjectDataProvider = + IProjectDataProvider & { + /** + * Returns the final verse number in the specified book and chapter using the project's + * versification. + */ + lookupFinalVerseNumber(bookNum: number, chapterNum: number): Promise; + /** Returns the final chapter number in the specified book using the project's versification. */ + lookupFinalChapter(bookNum: number): Promise; + /** + * Returns an array of final verse numbers for every chapter in a book, indexed 1-based for + * ergonomic `result[chapterNum]` access (no off-by-one). Index 0 is a filler `0` — it is not + * a valid chapter. The returned array has length `lastChapter + 1`. + * + * Useful for pre-fetching all verse counts for a book in a single round trip — preferable to + * `lookupFinalVerseNumber` in a loop when the caller needs many chapters of the same book. + * + * @example + * + * ```typescript + * const finalVerses = await pdp.lookupFinalVerseNumbersInBook(1); // Genesis + * finalVerses[1]; // → 31 (last verse of Genesis 1) + * finalVerses[50]; // → 26 (last verse of Genesis 50) + * finalVerses[0]; // → 0 (filler; chapter 0 does not exist) + * ``` + */ + lookupFinalVerseNumbersInBook(bookNum: number): Promise; + }; // #endregion Versification Types @@ -1728,6 +1754,7 @@ declare module 'papi-shared-types' { IUSJVerseProjectDataProvider, IPlainTextVerseProjectDataProvider, IMarkerNamesProjectDataProvider, + IVersificationProjectDataProvider, IFindInScriptureProjectDataProvider, IReplaceWithUsfmProjectDataProvider, ICheckAggregatorService, @@ -1750,6 +1777,7 @@ declare module 'papi-shared-types' { 'platformScripture.USJ_Verse': IUSJVerseProjectDataProvider; 'platformScripture.PlainText_Verse': IPlainTextVerseProjectDataProvider; 'platformScripture.MarkerNames': IMarkerNamesProjectDataProvider; + 'platformScripture.Versification': IVersificationProjectDataProvider; 'platformScripture.findInScripture': IFindInScriptureProjectDataProvider; 'platformScripture.replaceWithUsfm': IReplaceWithUsfmProjectDataProvider; }