@aopslab/domain-dm-docman 0.1.2
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/LICENSE +202 -0
- package/NOTICE +6 -0
- package/README.md +26 -0
- package/dist/application/errors/AssetServiceError.d.ts +20 -0
- package/dist/application/errors/AssetServiceError.js +13 -0
- package/dist/application/errors/AssetVersionServiceError.d.ts +20 -0
- package/dist/application/errors/AssetVersionServiceError.js +13 -0
- package/dist/application/errors/DocumentGroupServiceError.d.ts +20 -0
- package/dist/application/errors/DocumentGroupServiceError.js +16 -0
- package/dist/application/errors/DocumentSectionLinkServiceError.d.ts +20 -0
- package/dist/application/errors/DocumentSectionLinkServiceError.js +16 -0
- package/dist/application/errors/DocumentServiceError.d.ts +20 -0
- package/dist/application/errors/DocumentServiceError.js +16 -0
- package/dist/application/errors/DocumentVersionServiceError.d.ts +20 -0
- package/dist/application/errors/DocumentVersionServiceError.js +16 -0
- package/dist/application/errors/EmbedServiceError.d.ts +20 -0
- package/dist/application/errors/EmbedServiceError.js +16 -0
- package/dist/application/errors/PageEmbedLinkServiceError.d.ts +20 -0
- package/dist/application/errors/PageEmbedLinkServiceError.js +16 -0
- package/dist/application/errors/PageServiceError.d.ts +20 -0
- package/dist/application/errors/PageServiceError.js +16 -0
- package/dist/application/errors/PageSnippetLinkServiceError.d.ts +20 -0
- package/dist/application/errors/PageSnippetLinkServiceError.js +16 -0
- package/dist/application/errors/PageVersionServiceError.d.ts +20 -0
- package/dist/application/errors/PageVersionServiceError.js +16 -0
- package/dist/application/errors/SectionPageLinkServiceError.d.ts +20 -0
- package/dist/application/errors/SectionPageLinkServiceError.js +16 -0
- package/dist/application/errors/SectionServiceError.d.ts +20 -0
- package/dist/application/errors/SectionServiceError.js +16 -0
- package/dist/application/errors/SnippetServiceError.d.ts +20 -0
- package/dist/application/errors/SnippetServiceError.js +16 -0
- package/dist/application/errors/index.d.ts +1 -0
- package/dist/application/errors/index.js +1 -0
- package/dist/application/factories/RepositoryFactoryAsset.d.ts +4 -0
- package/dist/application/factories/RepositoryFactoryAsset.js +7 -0
- package/dist/application/factories/RepositoryFactoryAssetVersion.d.ts +4 -0
- package/dist/application/factories/RepositoryFactoryAssetVersion.js +7 -0
- package/dist/application/factories/RepositoryFactoryDocument.d.ts +4 -0
- package/dist/application/factories/RepositoryFactoryDocument.js +7 -0
- package/dist/application/factories/RepositoryFactoryDocumentGroup.d.ts +4 -0
- package/dist/application/factories/RepositoryFactoryDocumentGroup.js +7 -0
- package/dist/application/factories/RepositoryFactoryDocumentIndexEntry.d.ts +4 -0
- package/dist/application/factories/RepositoryFactoryDocumentIndexEntry.js +7 -0
- package/dist/application/factories/RepositoryFactoryDocumentSectionLink.d.ts +4 -0
- package/dist/application/factories/RepositoryFactoryDocumentSectionLink.js +7 -0
- package/dist/application/factories/RepositoryFactoryDocumentVersion.d.ts +4 -0
- package/dist/application/factories/RepositoryFactoryDocumentVersion.js +7 -0
- package/dist/application/factories/RepositoryFactoryEmbed.d.ts +4 -0
- package/dist/application/factories/RepositoryFactoryEmbed.js +7 -0
- package/dist/application/factories/RepositoryFactoryPage.d.ts +4 -0
- package/dist/application/factories/RepositoryFactoryPage.js +7 -0
- package/dist/application/factories/RepositoryFactoryPageEmbedLink.d.ts +4 -0
- package/dist/application/factories/RepositoryFactoryPageEmbedLink.js +7 -0
- package/dist/application/factories/RepositoryFactoryPageSnippetLink.d.ts +4 -0
- package/dist/application/factories/RepositoryFactoryPageSnippetLink.js +7 -0
- package/dist/application/factories/RepositoryFactoryPageVersion.d.ts +4 -0
- package/dist/application/factories/RepositoryFactoryPageVersion.js +7 -0
- package/dist/application/factories/RepositoryFactorySection.d.ts +4 -0
- package/dist/application/factories/RepositoryFactorySection.js +7 -0
- package/dist/application/factories/RepositoryFactorySectionPageLink.d.ts +4 -0
- package/dist/application/factories/RepositoryFactorySectionPageLink.js +7 -0
- package/dist/application/factories/RepositoryFactorySnippet.d.ts +4 -0
- package/dist/application/factories/RepositoryFactorySnippet.js +7 -0
- package/dist/application/factories/ServiceAssetBuilder.d.ts +30 -0
- package/dist/application/factories/ServiceAssetBuilder.js +80 -0
- package/dist/application/factories/ServiceAssetFactory.d.ts +11 -0
- package/dist/application/factories/ServiceAssetFactory.js +15 -0
- package/dist/application/factories/ServiceAssetVersionBuilder.d.ts +30 -0
- package/dist/application/factories/ServiceAssetVersionBuilder.js +80 -0
- package/dist/application/factories/ServiceAssetVersionFactory.d.ts +11 -0
- package/dist/application/factories/ServiceAssetVersionFactory.js +15 -0
- package/dist/application/factories/ServiceDocumentBuilder.d.ts +30 -0
- package/dist/application/factories/ServiceDocumentBuilder.js +83 -0
- package/dist/application/factories/ServiceDocumentFactory.d.ts +11 -0
- package/dist/application/factories/ServiceDocumentFactory.js +15 -0
- package/dist/application/factories/ServiceDocumentGroupBuilder.d.ts +30 -0
- package/dist/application/factories/ServiceDocumentGroupBuilder.js +83 -0
- package/dist/application/factories/ServiceDocumentGroupFactory.d.ts +11 -0
- package/dist/application/factories/ServiceDocumentGroupFactory.js +15 -0
- package/dist/application/factories/ServiceDocumentSectionLinkBuilder.d.ts +30 -0
- package/dist/application/factories/ServiceDocumentSectionLinkBuilder.js +83 -0
- package/dist/application/factories/ServiceDocumentSectionLinkFactory.d.ts +11 -0
- package/dist/application/factories/ServiceDocumentSectionLinkFactory.js +15 -0
- package/dist/application/factories/ServiceDocumentVersionBuilder.d.ts +30 -0
- package/dist/application/factories/ServiceDocumentVersionBuilder.js +83 -0
- package/dist/application/factories/ServiceDocumentVersionFactory.d.ts +11 -0
- package/dist/application/factories/ServiceDocumentVersionFactory.js +15 -0
- package/dist/application/factories/ServiceEmbedBuilder.d.ts +30 -0
- package/dist/application/factories/ServiceEmbedBuilder.js +83 -0
- package/dist/application/factories/ServiceEmbedFactory.d.ts +11 -0
- package/dist/application/factories/ServiceEmbedFactory.js +15 -0
- package/dist/application/factories/ServicePageBuilder.d.ts +30 -0
- package/dist/application/factories/ServicePageBuilder.js +83 -0
- package/dist/application/factories/ServicePageEmbedLinkBuilder.d.ts +30 -0
- package/dist/application/factories/ServicePageEmbedLinkBuilder.js +83 -0
- package/dist/application/factories/ServicePageEmbedLinkFactory.d.ts +11 -0
- package/dist/application/factories/ServicePageEmbedLinkFactory.js +15 -0
- package/dist/application/factories/ServicePageFactory.d.ts +11 -0
- package/dist/application/factories/ServicePageFactory.js +15 -0
- package/dist/application/factories/ServicePageSnippetLinkBuilder.d.ts +30 -0
- package/dist/application/factories/ServicePageSnippetLinkBuilder.js +83 -0
- package/dist/application/factories/ServicePageSnippetLinkFactory.d.ts +11 -0
- package/dist/application/factories/ServicePageSnippetLinkFactory.js +15 -0
- package/dist/application/factories/ServicePageVersionBuilder.d.ts +34 -0
- package/dist/application/factories/ServicePageVersionBuilder.js +121 -0
- package/dist/application/factories/ServicePageVersionFactory.d.ts +11 -0
- package/dist/application/factories/ServicePageVersionFactory.js +15 -0
- package/dist/application/factories/ServiceSectionBuilder.d.ts +30 -0
- package/dist/application/factories/ServiceSectionBuilder.js +83 -0
- package/dist/application/factories/ServiceSectionFactory.d.ts +11 -0
- package/dist/application/factories/ServiceSectionFactory.js +15 -0
- package/dist/application/factories/ServiceSectionPageLinkBuilder.d.ts +30 -0
- package/dist/application/factories/ServiceSectionPageLinkBuilder.js +83 -0
- package/dist/application/factories/ServiceSectionPageLinkFactory.d.ts +11 -0
- package/dist/application/factories/ServiceSectionPageLinkFactory.js +15 -0
- package/dist/application/factories/ServiceSnippetBuilder.d.ts +30 -0
- package/dist/application/factories/ServiceSnippetBuilder.js +83 -0
- package/dist/application/factories/ServiceSnippetFactory.d.ts +11 -0
- package/dist/application/factories/ServiceSnippetFactory.js +15 -0
- package/dist/application/factories/drizzleDialect.d.ts +16 -0
- package/dist/application/factories/drizzleDialect.js +36 -0
- package/dist/application/factories/index.d.ts +43 -0
- package/dist/application/factories/index.js +44 -0
- package/dist/application/ports/inbound/IAssetServicePort.d.ts +14 -0
- package/dist/application/ports/inbound/IAssetServicePort.js +1 -0
- package/dist/application/ports/inbound/IAssetVersionServicePort.d.ts +14 -0
- package/dist/application/ports/inbound/IAssetVersionServicePort.js +1 -0
- package/dist/application/ports/inbound/IDocumentGroupServicePort.d.ts +14 -0
- package/dist/application/ports/inbound/IDocumentGroupServicePort.js +1 -0
- package/dist/application/ports/inbound/IDocumentSectionLinkServicePort.d.ts +16 -0
- package/dist/application/ports/inbound/IDocumentSectionLinkServicePort.js +1 -0
- package/dist/application/ports/inbound/IDocumentServicePort.d.ts +300 -0
- package/dist/application/ports/inbound/IDocumentServicePort.js +1 -0
- package/dist/application/ports/inbound/IDocumentVersionServicePort.d.ts +106 -0
- package/dist/application/ports/inbound/IDocumentVersionServicePort.js +1 -0
- package/dist/application/ports/inbound/IEmbedServicePort.d.ts +14 -0
- package/dist/application/ports/inbound/IEmbedServicePort.js +1 -0
- package/dist/application/ports/inbound/IPageEmbedLinkServicePort.d.ts +14 -0
- package/dist/application/ports/inbound/IPageEmbedLinkServicePort.js +1 -0
- package/dist/application/ports/inbound/IPageServicePort.d.ts +14 -0
- package/dist/application/ports/inbound/IPageServicePort.js +1 -0
- package/dist/application/ports/inbound/IPageSnippetLinkServicePort.d.ts +14 -0
- package/dist/application/ports/inbound/IPageSnippetLinkServicePort.js +1 -0
- package/dist/application/ports/inbound/IPageVersionServicePort.d.ts +14 -0
- package/dist/application/ports/inbound/IPageVersionServicePort.js +1 -0
- package/dist/application/ports/inbound/ISectionPageLinkServicePort.d.ts +14 -0
- package/dist/application/ports/inbound/ISectionPageLinkServicePort.js +1 -0
- package/dist/application/ports/inbound/ISectionServicePort.d.ts +14 -0
- package/dist/application/ports/inbound/ISectionServicePort.js +1 -0
- package/dist/application/ports/inbound/ISnippetServicePort.d.ts +14 -0
- package/dist/application/ports/inbound/ISnippetServicePort.js +1 -0
- package/dist/application/ports/inbound/index.d.ts +14 -0
- package/dist/application/ports/inbound/index.js +15 -0
- package/dist/application/ports/repository-ports/IRepositoryPortAsset.d.ts +5 -0
- package/dist/application/ports/repository-ports/IRepositoryPortAsset.js +1 -0
- package/dist/application/ports/repository-ports/IRepositoryPortAssetVersion.d.ts +5 -0
- package/dist/application/ports/repository-ports/IRepositoryPortAssetVersion.js +1 -0
- package/dist/application/ports/repository-ports/IRepositoryPortDocument.d.ts +10 -0
- package/dist/application/ports/repository-ports/IRepositoryPortDocument.js +1 -0
- package/dist/application/ports/repository-ports/IRepositoryPortDocumentGroup.d.ts +10 -0
- package/dist/application/ports/repository-ports/IRepositoryPortDocumentGroup.js +1 -0
- package/dist/application/ports/repository-ports/IRepositoryPortDocumentIndexEntry.d.ts +5 -0
- package/dist/application/ports/repository-ports/IRepositoryPortDocumentIndexEntry.js +1 -0
- package/dist/application/ports/repository-ports/IRepositoryPortDocumentSectionLink.d.ts +21 -0
- package/dist/application/ports/repository-ports/IRepositoryPortDocumentSectionLink.js +1 -0
- package/dist/application/ports/repository-ports/IRepositoryPortDocumentVersion.d.ts +10 -0
- package/dist/application/ports/repository-ports/IRepositoryPortDocumentVersion.js +1 -0
- package/dist/application/ports/repository-ports/IRepositoryPortEmbed.d.ts +10 -0
- package/dist/application/ports/repository-ports/IRepositoryPortEmbed.js +1 -0
- package/dist/application/ports/repository-ports/IRepositoryPortPage.d.ts +10 -0
- package/dist/application/ports/repository-ports/IRepositoryPortPage.js +1 -0
- package/dist/application/ports/repository-ports/IRepositoryPortPageEmbedLink.d.ts +10 -0
- package/dist/application/ports/repository-ports/IRepositoryPortPageEmbedLink.js +1 -0
- package/dist/application/ports/repository-ports/IRepositoryPortPageSnippetLink.d.ts +10 -0
- package/dist/application/ports/repository-ports/IRepositoryPortPageSnippetLink.js +1 -0
- package/dist/application/ports/repository-ports/IRepositoryPortPageVersion.d.ts +10 -0
- package/dist/application/ports/repository-ports/IRepositoryPortPageVersion.js +1 -0
- package/dist/application/ports/repository-ports/IRepositoryPortSection.d.ts +10 -0
- package/dist/application/ports/repository-ports/IRepositoryPortSection.js +1 -0
- package/dist/application/ports/repository-ports/IRepositoryPortSectionPageLink.d.ts +10 -0
- package/dist/application/ports/repository-ports/IRepositoryPortSectionPageLink.js +1 -0
- package/dist/application/ports/repository-ports/IRepositoryPortSnippet.d.ts +10 -0
- package/dist/application/ports/repository-ports/IRepositoryPortSnippet.js +1 -0
- package/dist/application/ports/repository-ports/index.d.ts +15 -0
- package/dist/application/ports/repository-ports/index.js +16 -0
- package/dist/application/services/documentCascadeDelete.d.ts +47 -0
- package/dist/application/services/documentCascadeDelete.js +202 -0
- package/dist/application/services/documentComposeSupport.d.ts +30 -0
- package/dist/application/services/documentComposeSupport.js +285 -0
- package/dist/application/services/documentIndexSupport.d.ts +7 -0
- package/dist/application/services/documentIndexSupport.js +28 -0
- package/dist/application/services/documentPublishSupport.d.ts +14 -0
- package/dist/application/services/documentPublishSupport.js +39 -0
- package/dist/application/services/documentRetrievalAiSupport.d.ts +21 -0
- package/dist/application/services/documentRetrievalAiSupport.js +114 -0
- package/dist/application/services/index.d.ts +14 -0
- package/dist/application/services/index.js +15 -0
- package/dist/application/services/service.asset.d.ts +25 -0
- package/dist/application/services/service.asset.js +58 -0
- package/dist/application/services/service.assetVersion.d.ts +25 -0
- package/dist/application/services/service.assetVersion.js +58 -0
- package/dist/application/services/service.document.d.ts +167 -0
- package/dist/application/services/service.document.js +2860 -0
- package/dist/application/services/service.documentGroup.d.ts +25 -0
- package/dist/application/services/service.documentGroup.js +58 -0
- package/dist/application/services/service.documentSectionLink.d.ts +27 -0
- package/dist/application/services/service.documentSectionLink.js +69 -0
- package/dist/application/services/service.documentVersion.d.ts +66 -0
- package/dist/application/services/service.documentVersion.js +715 -0
- package/dist/application/services/service.embed.d.ts +25 -0
- package/dist/application/services/service.embed.js +58 -0
- package/dist/application/services/service.page.d.ts +25 -0
- package/dist/application/services/service.page.js +58 -0
- package/dist/application/services/service.pageEmbedLink.d.ts +25 -0
- package/dist/application/services/service.pageEmbedLink.js +58 -0
- package/dist/application/services/service.pageSnippetLink.d.ts +25 -0
- package/dist/application/services/service.pageSnippetLink.js +58 -0
- package/dist/application/services/service.pageVersion.d.ts +31 -0
- package/dist/application/services/service.pageVersion.js +114 -0
- package/dist/application/services/service.section.d.ts +25 -0
- package/dist/application/services/service.section.js +58 -0
- package/dist/application/services/service.sectionPageLink.d.ts +25 -0
- package/dist/application/services/service.sectionPageLink.js +58 -0
- package/dist/application/services/service.snippet.d.ts +25 -0
- package/dist/application/services/service.snippet.js +58 -0
- package/dist/application/services/service.zod-validation.d.ts +11 -0
- package/dist/application/services/service.zod-validation.js +14 -0
- package/dist/domain/domain.d.ts +8 -0
- package/dist/domain/domain.js +9 -0
- package/dist/domain/dto/index.d.ts +1 -0
- package/dist/domain/dto/index.js +1 -0
- package/dist/domain/index.d.ts +4 -0
- package/dist/domain/index.js +4 -0
- package/dist/domain/models/asset/IbmAsset.d.ts +5 -0
- package/dist/domain/models/asset/IbmAsset.js +18 -0
- package/dist/domain/models/asset/index.d.ts +2 -0
- package/dist/domain/models/asset/index.js +2 -0
- package/dist/domain/models/asset/zod.schema.d.ts +39 -0
- package/dist/domain/models/asset/zod.schema.js +22 -0
- package/dist/domain/models/assetVersion/IbmAssetVersion.d.ts +6 -0
- package/dist/domain/models/assetVersion/IbmAssetVersion.js +25 -0
- package/dist/domain/models/assetVersion/index.d.ts +2 -0
- package/dist/domain/models/assetVersion/index.js +2 -0
- package/dist/domain/models/assetVersion/zod.schema.d.ts +71 -0
- package/dist/domain/models/assetVersion/zod.schema.js +37 -0
- package/dist/domain/models/document/IbmDocument.d.ts +8 -0
- package/dist/domain/models/document/IbmDocument.js +30 -0
- package/dist/domain/models/document/index.d.ts +2 -0
- package/dist/domain/models/document/index.js +2 -0
- package/dist/domain/models/document/zod.schema.d.ts +157 -0
- package/dist/domain/models/document/zod.schema.js +31 -0
- package/dist/domain/models/documentGroup/IbmDocumentGroup.d.ts +5 -0
- package/dist/domain/models/documentGroup/IbmDocumentGroup.js +17 -0
- package/dist/domain/models/documentGroup/index.d.ts +2 -0
- package/dist/domain/models/documentGroup/index.js +2 -0
- package/dist/domain/models/documentGroup/zod.schema.d.ts +27 -0
- package/dist/domain/models/documentGroup/zod.schema.js +22 -0
- package/dist/domain/models/documentIndexEntry/IbmDocumentIndexEntry.d.ts +6 -0
- package/dist/domain/models/documentIndexEntry/IbmDocumentIndexEntry.js +50 -0
- package/dist/domain/models/documentIndexEntry/index.d.ts +2 -0
- package/dist/domain/models/documentIndexEntry/index.js +2 -0
- package/dist/domain/models/documentIndexEntry/zod.schema.d.ts +112 -0
- package/dist/domain/models/documentIndexEntry/zod.schema.js +55 -0
- package/dist/domain/models/documentSectionLink/IbmDocumentSectionLink.d.ts +5 -0
- package/dist/domain/models/documentSectionLink/IbmDocumentSectionLink.js +23 -0
- package/dist/domain/models/documentSectionLink/index.d.ts +2 -0
- package/dist/domain/models/documentSectionLink/index.js +2 -0
- package/dist/domain/models/documentSectionLink/zod.schema.d.ts +65 -0
- package/dist/domain/models/documentSectionLink/zod.schema.js +80 -0
- package/dist/domain/models/documentVersion/IbmDocumentVersion.d.ts +8 -0
- package/dist/domain/models/documentVersion/IbmDocumentVersion.js +25 -0
- package/dist/domain/models/documentVersion/index.d.ts +2 -0
- package/dist/domain/models/documentVersion/index.js +2 -0
- package/dist/domain/models/documentVersion/zod.schema.d.ts +75 -0
- package/dist/domain/models/documentVersion/zod.schema.js +26 -0
- package/dist/domain/models/embed/IbmEmbed.d.ts +5 -0
- package/dist/domain/models/embed/IbmEmbed.js +19 -0
- package/dist/domain/models/embed/index.d.ts +2 -0
- package/dist/domain/models/embed/index.js +2 -0
- package/dist/domain/models/embed/zod.schema.d.ts +39 -0
- package/dist/domain/models/embed/zod.schema.js +24 -0
- package/dist/domain/models/index.d.ts +15 -0
- package/dist/domain/models/index.js +16 -0
- package/dist/domain/models/page/IbmPage.d.ts +8 -0
- package/dist/domain/models/page/IbmPage.js +20 -0
- package/dist/domain/models/page/index.d.ts +2 -0
- package/dist/domain/models/page/index.js +2 -0
- package/dist/domain/models/page/zod.schema.d.ts +57 -0
- package/dist/domain/models/page/zod.schema.js +21 -0
- package/dist/domain/models/pageEmbedLink/IbmPageEmbedLink.d.ts +5 -0
- package/dist/domain/models/pageEmbedLink/IbmPageEmbedLink.js +14 -0
- package/dist/domain/models/pageEmbedLink/index.d.ts +2 -0
- package/dist/domain/models/pageEmbedLink/index.js +2 -0
- package/dist/domain/models/pageEmbedLink/zod.schema.d.ts +21 -0
- package/dist/domain/models/pageEmbedLink/zod.schema.js +19 -0
- package/dist/domain/models/pageSnippetLink/IbmPageSnippetLink.d.ts +5 -0
- package/dist/domain/models/pageSnippetLink/IbmPageSnippetLink.js +15 -0
- package/dist/domain/models/pageSnippetLink/index.d.ts +2 -0
- package/dist/domain/models/pageSnippetLink/index.js +2 -0
- package/dist/domain/models/pageSnippetLink/zod.schema.d.ts +23 -0
- package/dist/domain/models/pageSnippetLink/zod.schema.js +20 -0
- package/dist/domain/models/pageVersion/IbmPageVersion.d.ts +8 -0
- package/dist/domain/models/pageVersion/IbmPageVersion.js +23 -0
- package/dist/domain/models/pageVersion/index.d.ts +2 -0
- package/dist/domain/models/pageVersion/index.js +2 -0
- package/dist/domain/models/pageVersion/zod.schema.d.ts +77 -0
- package/dist/domain/models/pageVersion/zod.schema.js +24 -0
- package/dist/domain/models/section/IbmSection.d.ts +8 -0
- package/dist/domain/models/section/IbmSection.js +20 -0
- package/dist/domain/models/section/index.d.ts +2 -0
- package/dist/domain/models/section/index.js +2 -0
- package/dist/domain/models/section/zod.schema.d.ts +57 -0
- package/dist/domain/models/section/zod.schema.js +21 -0
- package/dist/domain/models/sectionPageLink/IbmSectionPageLink.d.ts +5 -0
- package/dist/domain/models/sectionPageLink/IbmSectionPageLink.js +18 -0
- package/dist/domain/models/sectionPageLink/index.d.ts +2 -0
- package/dist/domain/models/sectionPageLink/index.js +2 -0
- package/dist/domain/models/sectionPageLink/zod.schema.d.ts +29 -0
- package/dist/domain/models/sectionPageLink/zod.schema.js +23 -0
- package/dist/domain/models/snippet/IbmSnippet.d.ts +5 -0
- package/dist/domain/models/snippet/IbmSnippet.js +17 -0
- package/dist/domain/models/snippet/index.d.ts +2 -0
- package/dist/domain/models/snippet/index.js +2 -0
- package/dist/domain/models/snippet/zod.schema.d.ts +27 -0
- package/dist/domain/models/snippet/zod.schema.js +22 -0
- package/dist/domain/types.d.ts +1 -0
- package/dist/domain/types.js +1 -0
- package/dist/index.d.ts +3 -0
- package/dist/index.js +3 -0
- package/dist/infrastructure/db/asset/drizzle/drizzle.mapper.asset.d.ts +31 -0
- package/dist/infrastructure/db/asset/drizzle/drizzle.mapper.asset.js +6 -0
- package/dist/infrastructure/db/asset/drizzle/drizzle.schema.asset.d.ts +250 -0
- package/dist/infrastructure/db/asset/drizzle/drizzle.schema.asset.js +24 -0
- package/dist/infrastructure/db/asset/drizzle/drizzle.schema.asset.sqlite.d.ts +272 -0
- package/dist/infrastructure/db/asset/drizzle/drizzle.schema.asset.sqlite.js +25 -0
- package/dist/infrastructure/db/asset/drizzle/index.d.ts +2 -0
- package/dist/infrastructure/db/asset/drizzle/index.js +2 -0
- package/dist/infrastructure/db/assetVersion/drizzle/drizzle.mapper.assetVersion.d.ts +45 -0
- package/dist/infrastructure/db/assetVersion/drizzle/drizzle.mapper.assetVersion.js +6 -0
- package/dist/infrastructure/db/assetVersion/drizzle/drizzle.schema.assetVersion.d.ts +371 -0
- package/dist/infrastructure/db/assetVersion/drizzle/drizzle.schema.assetVersion.js +30 -0
- package/dist/infrastructure/db/assetVersion/drizzle/drizzle.schema.assetVersion.sqlite.d.ts +397 -0
- package/dist/infrastructure/db/assetVersion/drizzle/drizzle.schema.assetVersion.sqlite.js +31 -0
- package/dist/infrastructure/db/assetVersion/drizzle/index.d.ts +2 -0
- package/dist/infrastructure/db/assetVersion/drizzle/index.js +2 -0
- package/dist/infrastructure/db/document/drizzle/drizzle.mapper.document.d.ts +98 -0
- package/dist/infrastructure/db/document/drizzle/drizzle.mapper.document.js +6 -0
- package/dist/infrastructure/db/document/drizzle/drizzle.schema.document.d.ts +394 -0
- package/dist/infrastructure/db/document/drizzle/drizzle.schema.document.js +34 -0
- package/dist/infrastructure/db/document/drizzle/drizzle.schema.document.sqlite.d.ts +424 -0
- package/dist/infrastructure/db/document/drizzle/drizzle.schema.document.sqlite.js +35 -0
- package/dist/infrastructure/db/document/drizzle/index.d.ts +2 -0
- package/dist/infrastructure/db/document/drizzle/index.js +2 -0
- package/dist/infrastructure/db/documentGroup/drizzle/drizzle.mapper.documentGroup.d.ts +29 -0
- package/dist/infrastructure/db/documentGroup/drizzle/drizzle.mapper.documentGroup.js +6 -0
- package/dist/infrastructure/db/documentGroup/drizzle/drizzle.schema.documentGroup.d.ts +233 -0
- package/dist/infrastructure/db/documentGroup/drizzle/drizzle.schema.documentGroup.js +22 -0
- package/dist/infrastructure/db/documentGroup/drizzle/drizzle.schema.documentGroup.sqlite.d.ts +253 -0
- package/dist/infrastructure/db/documentGroup/drizzle/drizzle.schema.documentGroup.sqlite.js +23 -0
- package/dist/infrastructure/db/documentGroup/drizzle/index.d.ts +2 -0
- package/dist/infrastructure/db/documentGroup/drizzle/index.js +2 -0
- package/dist/infrastructure/db/documentIndexEntry/drizzle/drizzle.mapper.documentIndexEntry.d.ts +95 -0
- package/dist/infrastructure/db/documentIndexEntry/drizzle/drizzle.mapper.documentIndexEntry.js +12 -0
- package/dist/infrastructure/db/documentIndexEntry/drizzle/drizzle.schema.documentIndexEntry.d.ts +792 -0
- package/dist/infrastructure/db/documentIndexEntry/drizzle/drizzle.schema.documentIndexEntry.js +57 -0
- package/dist/infrastructure/db/documentIndexEntry/drizzle/drizzle.schema.documentIndexEntry.sqlite.d.ts +854 -0
- package/dist/infrastructure/db/documentIndexEntry/drizzle/drizzle.schema.documentIndexEntry.sqlite.js +58 -0
- package/dist/infrastructure/db/documentIndexEntry/drizzle/index.d.ts +2 -0
- package/dist/infrastructure/db/documentIndexEntry/drizzle/index.js +2 -0
- package/dist/infrastructure/db/documentSectionLink/drizzle/drizzle.mapper.documentSectionLink.d.ts +41 -0
- package/dist/infrastructure/db/documentSectionLink/drizzle/drizzle.mapper.documentSectionLink.js +12 -0
- package/dist/infrastructure/db/documentSectionLink/drizzle/drizzle.schema.documentSectionLink.d.ts +335 -0
- package/dist/infrastructure/db/documentSectionLink/drizzle/drizzle.schema.documentSectionLink.js +29 -0
- package/dist/infrastructure/db/documentSectionLink/drizzle/drizzle.schema.documentSectionLink.sqlite.d.ts +357 -0
- package/dist/infrastructure/db/documentSectionLink/drizzle/drizzle.schema.documentSectionLink.sqlite.js +30 -0
- package/dist/infrastructure/db/documentSectionLink/drizzle/index.d.ts +2 -0
- package/dist/infrastructure/db/documentSectionLink/drizzle/index.js +2 -0
- package/dist/infrastructure/db/documentVersion/drizzle/drizzle.mapper.documentVersion.d.ts +54 -0
- package/dist/infrastructure/db/documentVersion/drizzle/drizzle.mapper.documentVersion.js +7 -0
- package/dist/infrastructure/db/documentVersion/drizzle/drizzle.schema.documentVersion.d.ts +301 -0
- package/dist/infrastructure/db/documentVersion/drizzle/drizzle.schema.documentVersion.js +30 -0
- package/dist/infrastructure/db/documentVersion/drizzle/drizzle.schema.documentVersion.sqlite.d.ts +323 -0
- package/dist/infrastructure/db/documentVersion/drizzle/drizzle.schema.documentVersion.sqlite.js +27 -0
- package/dist/infrastructure/db/documentVersion/drizzle/index.d.ts +2 -0
- package/dist/infrastructure/db/documentVersion/drizzle/index.js +2 -0
- package/dist/infrastructure/db/domain-naming.d.ts +2 -0
- package/dist/infrastructure/db/domain-naming.js +57 -0
- package/dist/infrastructure/db/embed/drizzle/drizzle.mapper.embed.d.ts +33 -0
- package/dist/infrastructure/db/embed/drizzle/drizzle.mapper.embed.js +5 -0
- package/dist/infrastructure/db/embed/drizzle/drizzle.schema.embed.d.ts +267 -0
- package/dist/infrastructure/db/embed/drizzle/drizzle.schema.embed.js +24 -0
- package/dist/infrastructure/db/embed/drizzle/drizzle.schema.embed.sqlite.d.ts +291 -0
- package/dist/infrastructure/db/embed/drizzle/drizzle.schema.embed.sqlite.js +25 -0
- package/dist/infrastructure/db/embed/drizzle/index.d.ts +2 -0
- package/dist/infrastructure/db/embed/drizzle/index.js +2 -0
- package/dist/infrastructure/db/page/drizzle/drizzle.mapper.page.d.ts +44 -0
- package/dist/infrastructure/db/page/drizzle/drizzle.mapper.page.js +5 -0
- package/dist/infrastructure/db/page/drizzle/drizzle.schema.page.d.ts +218 -0
- package/dist/infrastructure/db/page/drizzle/drizzle.schema.page.js +20 -0
- package/dist/infrastructure/db/page/drizzle/drizzle.schema.page.sqlite.d.ts +234 -0
- package/dist/infrastructure/db/page/drizzle/drizzle.schema.page.sqlite.js +21 -0
- package/dist/infrastructure/db/page/drizzle/index.d.ts +2 -0
- package/dist/infrastructure/db/page/drizzle/index.js +2 -0
- package/dist/infrastructure/db/pageEmbedLink/drizzle/drizzle.mapper.pageEmbedLink.d.ts +23 -0
- package/dist/infrastructure/db/pageEmbedLink/drizzle/drizzle.mapper.pageEmbedLink.js +5 -0
- package/dist/infrastructure/db/pageEmbedLink/drizzle/drizzle.schema.pageEmbedLink.d.ts +180 -0
- package/dist/infrastructure/db/pageEmbedLink/drizzle/drizzle.schema.pageEmbedLink.js +18 -0
- package/dist/infrastructure/db/pageEmbedLink/drizzle/drizzle.schema.pageEmbedLink.sqlite.d.ts +194 -0
- package/dist/infrastructure/db/pageEmbedLink/drizzle/drizzle.schema.pageEmbedLink.sqlite.js +19 -0
- package/dist/infrastructure/db/pageEmbedLink/drizzle/index.d.ts +2 -0
- package/dist/infrastructure/db/pageEmbedLink/drizzle/index.js +2 -0
- package/dist/infrastructure/db/pageSnippetLink/drizzle/drizzle.mapper.pageSnippetLink.d.ts +25 -0
- package/dist/infrastructure/db/pageSnippetLink/drizzle/drizzle.mapper.pageSnippetLink.js +7 -0
- package/dist/infrastructure/db/pageSnippetLink/drizzle/drizzle.schema.pageSnippetLink.d.ts +197 -0
- package/dist/infrastructure/db/pageSnippetLink/drizzle/drizzle.schema.pageSnippetLink.js +19 -0
- package/dist/infrastructure/db/pageSnippetLink/drizzle/drizzle.schema.pageSnippetLink.sqlite.d.ts +211 -0
- package/dist/infrastructure/db/pageSnippetLink/drizzle/drizzle.schema.pageSnippetLink.sqlite.js +20 -0
- package/dist/infrastructure/db/pageSnippetLink/drizzle/index.d.ts +2 -0
- package/dist/infrastructure/db/pageSnippetLink/drizzle/index.js +2 -0
- package/dist/infrastructure/db/pageVersion/drizzle/drizzle.mapper.pageVersion.d.ts +50 -0
- package/dist/infrastructure/db/pageVersion/drizzle/drizzle.mapper.pageVersion.js +6 -0
- package/dist/infrastructure/db/pageVersion/drizzle/drizzle.schema.pageVersion.d.ts +271 -0
- package/dist/infrastructure/db/pageVersion/drizzle/drizzle.schema.pageVersion.js +23 -0
- package/dist/infrastructure/db/pageVersion/drizzle/drizzle.schema.pageVersion.sqlite.d.ts +289 -0
- package/dist/infrastructure/db/pageVersion/drizzle/drizzle.schema.pageVersion.sqlite.js +24 -0
- package/dist/infrastructure/db/pageVersion/drizzle/index.d.ts +2 -0
- package/dist/infrastructure/db/pageVersion/drizzle/index.js +2 -0
- package/dist/infrastructure/db/section/drizzle/drizzle.mapper.section.d.ts +44 -0
- package/dist/infrastructure/db/section/drizzle/drizzle.mapper.section.js +5 -0
- package/dist/infrastructure/db/section/drizzle/drizzle.schema.section.d.ts +216 -0
- package/dist/infrastructure/db/section/drizzle/drizzle.schema.section.js +20 -0
- package/dist/infrastructure/db/section/drizzle/drizzle.schema.section.sqlite.d.ts +234 -0
- package/dist/infrastructure/db/section/drizzle/drizzle.schema.section.sqlite.js +21 -0
- package/dist/infrastructure/db/section/drizzle/index.d.ts +2 -0
- package/dist/infrastructure/db/section/drizzle/index.js +2 -0
- package/dist/infrastructure/db/sectionPageLink/drizzle/drizzle.mapper.sectionPageLink.d.ts +31 -0
- package/dist/infrastructure/db/sectionPageLink/drizzle/drizzle.mapper.sectionPageLink.js +7 -0
- package/dist/infrastructure/db/sectionPageLink/drizzle/drizzle.schema.sectionPageLink.d.ts +248 -0
- package/dist/infrastructure/db/sectionPageLink/drizzle/drizzle.schema.sectionPageLink.js +22 -0
- package/dist/infrastructure/db/sectionPageLink/drizzle/drizzle.schema.sectionPageLink.sqlite.d.ts +264 -0
- package/dist/infrastructure/db/sectionPageLink/drizzle/drizzle.schema.sectionPageLink.sqlite.js +23 -0
- package/dist/infrastructure/db/sectionPageLink/drizzle/index.d.ts +2 -0
- package/dist/infrastructure/db/sectionPageLink/drizzle/index.js +2 -0
- package/dist/infrastructure/db/snippet/drizzle/drizzle.mapper.snippet.d.ts +29 -0
- package/dist/infrastructure/db/snippet/drizzle/drizzle.mapper.snippet.js +5 -0
- package/dist/infrastructure/db/snippet/drizzle/drizzle.schema.snippet.d.ts +233 -0
- package/dist/infrastructure/db/snippet/drizzle/drizzle.schema.snippet.js +22 -0
- package/dist/infrastructure/db/snippet/drizzle/drizzle.schema.snippet.sqlite.d.ts +253 -0
- package/dist/infrastructure/db/snippet/drizzle/drizzle.schema.snippet.sqlite.js +23 -0
- package/dist/infrastructure/db/snippet/drizzle/index.d.ts +2 -0
- package/dist/infrastructure/db/snippet/drizzle/index.js +2 -0
- package/dist/infrastructure/repositories/asset/drizzle/AssetDrizzleRepo.d.ts +12 -0
- package/dist/infrastructure/repositories/asset/drizzle/AssetDrizzleRepo.js +8 -0
- package/dist/infrastructure/repositories/asset/drizzle/AssetDrizzleSqliteRepo.d.ts +12 -0
- package/dist/infrastructure/repositories/asset/drizzle/AssetDrizzleSqliteRepo.js +8 -0
- package/dist/infrastructure/repositories/assetVersion/drizzle/AssetVersionDrizzleRepo.d.ts +12 -0
- package/dist/infrastructure/repositories/assetVersion/drizzle/AssetVersionDrizzleRepo.js +8 -0
- package/dist/infrastructure/repositories/assetVersion/drizzle/AssetVersionDrizzleSqliteRepo.d.ts +12 -0
- package/dist/infrastructure/repositories/assetVersion/drizzle/AssetVersionDrizzleSqliteRepo.js +12 -0
- package/dist/infrastructure/repositories/document/drizzle/DocumentDrizzleRepo.d.ts +12 -0
- package/dist/infrastructure/repositories/document/drizzle/DocumentDrizzleRepo.js +8 -0
- package/dist/infrastructure/repositories/document/drizzle/DocumentDrizzleSqliteRepo.d.ts +12 -0
- package/dist/infrastructure/repositories/document/drizzle/DocumentDrizzleSqliteRepo.js +8 -0
- package/dist/infrastructure/repositories/documentGroup/drizzle/DocumentGroupDrizzleRepo.d.ts +12 -0
- package/dist/infrastructure/repositories/documentGroup/drizzle/DocumentGroupDrizzleRepo.js +8 -0
- package/dist/infrastructure/repositories/documentGroup/drizzle/DocumentGroupDrizzleSqliteRepo.d.ts +12 -0
- package/dist/infrastructure/repositories/documentGroup/drizzle/DocumentGroupDrizzleSqliteRepo.js +8 -0
- package/dist/infrastructure/repositories/documentIndexEntry/drizzle/DocumentIndexEntryDrizzleRepo.d.ts +12 -0
- package/dist/infrastructure/repositories/documentIndexEntry/drizzle/DocumentIndexEntryDrizzleRepo.js +12 -0
- package/dist/infrastructure/repositories/documentIndexEntry/drizzle/DocumentIndexEntryDrizzleSqliteRepo.d.ts +12 -0
- package/dist/infrastructure/repositories/documentIndexEntry/drizzle/DocumentIndexEntryDrizzleSqliteRepo.js +12 -0
- package/dist/infrastructure/repositories/documentSectionLink/drizzle/DocumentSectionLinkDrizzleRepo.d.ts +15 -0
- package/dist/infrastructure/repositories/documentSectionLink/drizzle/DocumentSectionLinkDrizzleRepo.js +41 -0
- package/dist/infrastructure/repositories/documentSectionLink/drizzle/DocumentSectionLinkDrizzleSqliteRepo.d.ts +15 -0
- package/dist/infrastructure/repositories/documentSectionLink/drizzle/DocumentSectionLinkDrizzleSqliteRepo.js +40 -0
- package/dist/infrastructure/repositories/documentVersion/drizzle/DocumentVersionDrizzleRepo.d.ts +12 -0
- package/dist/infrastructure/repositories/documentVersion/drizzle/DocumentVersionDrizzleRepo.js +8 -0
- package/dist/infrastructure/repositories/documentVersion/drizzle/DocumentVersionDrizzleSqliteRepo.d.ts +12 -0
- package/dist/infrastructure/repositories/documentVersion/drizzle/DocumentVersionDrizzleSqliteRepo.js +8 -0
- package/dist/infrastructure/repositories/embed/drizzle/EmbedDrizzleRepo.d.ts +12 -0
- package/dist/infrastructure/repositories/embed/drizzle/EmbedDrizzleRepo.js +8 -0
- package/dist/infrastructure/repositories/embed/drizzle/EmbedDrizzleSqliteRepo.d.ts +12 -0
- package/dist/infrastructure/repositories/embed/drizzle/EmbedDrizzleSqliteRepo.js +8 -0
- package/dist/infrastructure/repositories/index.d.ts +30 -0
- package/dist/infrastructure/repositories/index.js +31 -0
- package/dist/infrastructure/repositories/page/drizzle/PageDrizzleRepo.d.ts +12 -0
- package/dist/infrastructure/repositories/page/drizzle/PageDrizzleRepo.js +8 -0
- package/dist/infrastructure/repositories/page/drizzle/PageDrizzleSqliteRepo.d.ts +12 -0
- package/dist/infrastructure/repositories/page/drizzle/PageDrizzleSqliteRepo.js +8 -0
- package/dist/infrastructure/repositories/pageEmbedLink/drizzle/PageEmbedLinkDrizzleRepo.d.ts +12 -0
- package/dist/infrastructure/repositories/pageEmbedLink/drizzle/PageEmbedLinkDrizzleRepo.js +8 -0
- package/dist/infrastructure/repositories/pageEmbedLink/drizzle/PageEmbedLinkDrizzleSqliteRepo.d.ts +12 -0
- package/dist/infrastructure/repositories/pageEmbedLink/drizzle/PageEmbedLinkDrizzleSqliteRepo.js +8 -0
- package/dist/infrastructure/repositories/pageSnippetLink/drizzle/PageSnippetLinkDrizzleRepo.d.ts +12 -0
- package/dist/infrastructure/repositories/pageSnippetLink/drizzle/PageSnippetLinkDrizzleRepo.js +8 -0
- package/dist/infrastructure/repositories/pageSnippetLink/drizzle/PageSnippetLinkDrizzleSqliteRepo.d.ts +12 -0
- package/dist/infrastructure/repositories/pageSnippetLink/drizzle/PageSnippetLinkDrizzleSqliteRepo.js +8 -0
- package/dist/infrastructure/repositories/pageVersion/drizzle/PageVersionDrizzleRepo.d.ts +12 -0
- package/dist/infrastructure/repositories/pageVersion/drizzle/PageVersionDrizzleRepo.js +8 -0
- package/dist/infrastructure/repositories/pageVersion/drizzle/PageVersionDrizzleSqliteRepo.d.ts +12 -0
- package/dist/infrastructure/repositories/pageVersion/drizzle/PageVersionDrizzleSqliteRepo.js +8 -0
- package/dist/infrastructure/repositories/section/drizzle/SectionDrizzleRepo.d.ts +12 -0
- package/dist/infrastructure/repositories/section/drizzle/SectionDrizzleRepo.js +8 -0
- package/dist/infrastructure/repositories/section/drizzle/SectionDrizzleSqliteRepo.d.ts +12 -0
- package/dist/infrastructure/repositories/section/drizzle/SectionDrizzleSqliteRepo.js +8 -0
- package/dist/infrastructure/repositories/sectionPageLink/drizzle/SectionPageLinkDrizzleRepo.d.ts +12 -0
- package/dist/infrastructure/repositories/sectionPageLink/drizzle/SectionPageLinkDrizzleRepo.js +8 -0
- package/dist/infrastructure/repositories/sectionPageLink/drizzle/SectionPageLinkDrizzleSqliteRepo.d.ts +12 -0
- package/dist/infrastructure/repositories/sectionPageLink/drizzle/SectionPageLinkDrizzleSqliteRepo.js +8 -0
- package/dist/infrastructure/repositories/snippet/drizzle/SnippetDrizzleRepo.d.ts +12 -0
- package/dist/infrastructure/repositories/snippet/drizzle/SnippetDrizzleRepo.js +8 -0
- package/dist/infrastructure/repositories/snippet/drizzle/SnippetDrizzleSqliteRepo.d.ts +12 -0
- package/dist/infrastructure/repositories/snippet/drizzle/SnippetDrizzleSqliteRepo.js +8 -0
- package/dist/public/factories/index.d.ts +2 -0
- package/dist/public/factories/index.js +2 -0
- package/dist/public/index.d.ts +5 -0
- package/dist/public/index.js +5 -0
- package/dist/public/models/index.d.ts +3 -0
- package/dist/public/models/index.js +4 -0
- package/dist/public/ports/index.d.ts +1 -0
- package/dist/public/ports/index.js +2 -0
- package/dist/public/repository-ports/index.d.ts +1 -0
- package/dist/public/repository-ports/index.js +2 -0
- package/dist/public/services/index.d.ts +1 -0
- package/dist/public/services/index.js +2 -0
- package/dist/types.d.ts +1 -0
- package/dist/types.js +1 -0
- package/package.json +84 -0
|
@@ -0,0 +1,2860 @@
|
|
|
1
|
+
import { Effect } from 'effect';
|
|
2
|
+
import { pipe } from 'effect/Function';
|
|
3
|
+
import { createElement } from 'react';
|
|
4
|
+
import { renderToStaticMarkup } from 'react-dom/server';
|
|
5
|
+
import ReactMarkdown from 'react-markdown';
|
|
6
|
+
import remarkGfm from 'remark-gfm';
|
|
7
|
+
import { validateInput, XfErrorFactory, effectErrorInfo } from '@aopslab/xf-core';
|
|
8
|
+
import { documentZodSchemaInsert, } from '../../domain/models/index.js';
|
|
9
|
+
import { validateBmInputWithSchema } from './service.zod-validation.js';
|
|
10
|
+
import { mapDbError } from '@aopslab/xf-db';
|
|
11
|
+
import { listDocmanAssetReferenceTokens, normalizeDocmanComposeSourceContent, reduceDocmanComposeFormats, replaceDocmanAssetReferenceTokens, resolveDocmanComposeSourceFormat, splitDocmanComposeSourceContent, stripLeadingNumericPrefixForRender, } from './documentComposeSupport.js';
|
|
12
|
+
import { formatDocmanPublishTargets, resolveDocmanPublishTargetDescriptor, } from './documentPublishSupport.js';
|
|
13
|
+
import { DOCMAN_DOCUMENT_INDEX_BUILD_ACTOR, buildDocmanDocumentAnchor, buildDocmanDocumentIndexFingerprint, buildDocmanPageAnchor, buildDocmanSectionAnchor, normalizeDocmanDocumentIndexLocale, } from './documentIndexSupport.js';
|
|
14
|
+
import { deleteDocumentCascade, } from './documentCascadeDelete.js';
|
|
15
|
+
import { buildDocmanEmbeddingHash, cosineSimilarity, createDocmanLocalHashEmbeddingProvider, parseDocmanEmbeddingVector, resolveDocmanDefaultEmbeddingProvider, serializeDocmanEmbeddingVector, } from './documentRetrievalAiSupport.js';
|
|
16
|
+
const PAGE_BREAK_LINE_RE = /^\s*(?:<!--\s*(?:new\s*page|newpage|pagebreak)\s*-->|\[\[(?:new\s*page|newpage|pagebreak)\]\]|\[(?:new\s*page|newpage|pagebreak)\]|---\s*pagebreak\s*---|\\pagebreak)\s*$/i;
|
|
17
|
+
const HTML_LIKE_BLOCK_LINE_RE = /^<\/?[A-Za-z][A-Za-z0-9:_-]*(?:\s[^>]*)?>$/;
|
|
18
|
+
const HTML_COMMENT_LINE_RE = /^<!--[\s\S]*-->$/;
|
|
19
|
+
const DOCMAN_PAGEBREAK_HTML = '<hr data-docman-pagebreak="true" />';
|
|
20
|
+
const DOCMAN_PUBLISH_HTML_STYLES = `
|
|
21
|
+
:root {
|
|
22
|
+
color-scheme: light;
|
|
23
|
+
font-family: "IBM Plex Sans", "Segoe UI", sans-serif;
|
|
24
|
+
line-height: 1.6;
|
|
25
|
+
color: #16202a;
|
|
26
|
+
background: #f6f7f9;
|
|
27
|
+
}
|
|
28
|
+
body {
|
|
29
|
+
margin: 0;
|
|
30
|
+
padding: 32px 20px 48px;
|
|
31
|
+
background:
|
|
32
|
+
radial-gradient(circle at top left, rgba(15, 118, 110, 0.08), transparent 36%),
|
|
33
|
+
linear-gradient(180deg, #fbfcfd 0%, #f2f4f7 100%);
|
|
34
|
+
}
|
|
35
|
+
main {
|
|
36
|
+
max-width: 880px;
|
|
37
|
+
margin: 0 auto;
|
|
38
|
+
padding: 40px 48px;
|
|
39
|
+
background: rgba(255, 255, 255, 0.92);
|
|
40
|
+
border: 1px solid rgba(18, 24, 28, 0.08);
|
|
41
|
+
border-radius: 20px;
|
|
42
|
+
box-shadow: 0 18px 50px rgba(15, 23, 42, 0.08);
|
|
43
|
+
}
|
|
44
|
+
h1, h2, h3, h4, h5, h6 {
|
|
45
|
+
line-height: 1.2;
|
|
46
|
+
letter-spacing: -0.02em;
|
|
47
|
+
color: #0f172a;
|
|
48
|
+
}
|
|
49
|
+
h1 { font-size: 2.35rem; margin: 0 0 1.2rem; }
|
|
50
|
+
h2 { font-size: 1.72rem; margin-top: 2.2rem; }
|
|
51
|
+
h3 { font-size: 1.35rem; margin-top: 1.8rem; }
|
|
52
|
+
p, ul, ol, blockquote, table, pre {
|
|
53
|
+
margin-top: 0;
|
|
54
|
+
margin-bottom: 1rem;
|
|
55
|
+
}
|
|
56
|
+
a {
|
|
57
|
+
color: #0f766e;
|
|
58
|
+
}
|
|
59
|
+
blockquote {
|
|
60
|
+
margin-left: 0;
|
|
61
|
+
padding: 0.8rem 1rem;
|
|
62
|
+
border-left: 4px solid rgba(15, 118, 110, 0.45);
|
|
63
|
+
background: rgba(15, 118, 110, 0.06);
|
|
64
|
+
}
|
|
65
|
+
code {
|
|
66
|
+
font-family: "IBM Plex Mono", "SFMono-Regular", monospace;
|
|
67
|
+
font-size: 0.92em;
|
|
68
|
+
}
|
|
69
|
+
pre {
|
|
70
|
+
overflow-x: auto;
|
|
71
|
+
padding: 1rem 1.1rem;
|
|
72
|
+
border-radius: 14px;
|
|
73
|
+
background: #0f172a;
|
|
74
|
+
color: #e2e8f0;
|
|
75
|
+
}
|
|
76
|
+
table {
|
|
77
|
+
width: 100%;
|
|
78
|
+
border-collapse: collapse;
|
|
79
|
+
}
|
|
80
|
+
th, td {
|
|
81
|
+
padding: 0.65rem 0.8rem;
|
|
82
|
+
border: 1px solid rgba(15, 23, 42, 0.12);
|
|
83
|
+
text-align: left;
|
|
84
|
+
}
|
|
85
|
+
img {
|
|
86
|
+
max-width: 100%;
|
|
87
|
+
height: auto;
|
|
88
|
+
}
|
|
89
|
+
hr[data-docman-pagebreak="true"] {
|
|
90
|
+
margin: 2.5rem 0;
|
|
91
|
+
border: 0;
|
|
92
|
+
border-top: 2px dashed rgba(15, 23, 42, 0.18);
|
|
93
|
+
}
|
|
94
|
+
`;
|
|
95
|
+
const DOCMAN_DOCUMENT_SUMMARY_BUILD_ACTOR = 'docman:document-summary.build';
|
|
96
|
+
const activePersistedDocumentIndexBuilds = new Map();
|
|
97
|
+
export class DocumentService {
|
|
98
|
+
documentRepository;
|
|
99
|
+
assetRepository;
|
|
100
|
+
assetVersionRepository;
|
|
101
|
+
documentVersionRepository;
|
|
102
|
+
documentIndexEntryRepository;
|
|
103
|
+
documentSectionLinkRepository;
|
|
104
|
+
sectionRepository;
|
|
105
|
+
pageRepository;
|
|
106
|
+
pageVersionRepository;
|
|
107
|
+
sectionPageLinkRepository;
|
|
108
|
+
pageSnippetLinkRepository;
|
|
109
|
+
pageEmbedLinkRepository;
|
|
110
|
+
embeddingProvider;
|
|
111
|
+
logger;
|
|
112
|
+
locale;
|
|
113
|
+
constructor(options) {
|
|
114
|
+
const deps = options.serviceDependencies ?? {};
|
|
115
|
+
this.documentRepository = options.documentRepository;
|
|
116
|
+
this.assetRepository = deps.assetRepository;
|
|
117
|
+
this.assetVersionRepository = deps.assetVersionRepository;
|
|
118
|
+
this.documentVersionRepository = deps.documentVersionRepository;
|
|
119
|
+
this.documentIndexEntryRepository = deps.documentIndexEntryRepository;
|
|
120
|
+
this.documentSectionLinkRepository = deps.documentSectionLinkRepository;
|
|
121
|
+
this.sectionRepository = deps.sectionRepository;
|
|
122
|
+
this.pageRepository = deps.pageRepository;
|
|
123
|
+
this.pageVersionRepository = deps.pageVersionRepository;
|
|
124
|
+
this.sectionPageLinkRepository = deps.sectionPageLinkRepository;
|
|
125
|
+
this.pageSnippetLinkRepository = deps.pageSnippetLinkRepository;
|
|
126
|
+
this.pageEmbedLinkRepository = deps.pageEmbedLinkRepository;
|
|
127
|
+
this.embeddingProvider = deps.embeddingProvider ?? resolveDocmanDefaultEmbeddingProvider();
|
|
128
|
+
this.logger = options.logger?.child({ module: this.constructor.name });
|
|
129
|
+
this.locale = options.locale;
|
|
130
|
+
}
|
|
131
|
+
getById(id, options) {
|
|
132
|
+
const stage = 'DocumentService::getById';
|
|
133
|
+
const localeState = this.resolveLocaleOptions();
|
|
134
|
+
const queryOptions = this.withLocaleOptions(options, localeState, ['titleMl', 'summaryMl', 'descriptionMl']);
|
|
135
|
+
return pipe(validateInput(id, 'id', { stage }), Effect.flatMap((docId) => this.documentRepository.findById(docId, queryOptions).pipe(Effect.mapError(mapDbError({ stage, operation: 'findById', factory: XfErrorFactory.notFound })))), Effect.tapError((e) => Effect.sync(() => {
|
|
136
|
+
const info = effectErrorInfo(e);
|
|
137
|
+
this.logger?.error({ error: info.unwrapped, stage }, 'Error in getById');
|
|
138
|
+
})));
|
|
139
|
+
}
|
|
140
|
+
create(data) {
|
|
141
|
+
const stage = 'DocumentService::create';
|
|
142
|
+
return pipe(validateInput(data, 'data', { stage }), Effect.flatMap((payload) => validateBmInputWithSchema({
|
|
143
|
+
input: payload,
|
|
144
|
+
schema: documentZodSchemaInsert,
|
|
145
|
+
stage,
|
|
146
|
+
operation: 'DocumentService::create.documentZodSchemaInsert',
|
|
147
|
+
field: 'data',
|
|
148
|
+
})), Effect.flatMap((payload) => this.documentRepository.create(payload).pipe(Effect.mapError(mapDbError({ stage, operation: 'create', factory: XfErrorFactory.createFailed })))));
|
|
149
|
+
}
|
|
150
|
+
listDocuments(filter = {}, options = {}) {
|
|
151
|
+
const stage = 'DocumentService::listDocuments';
|
|
152
|
+
const includeVersionInfo = options?.includeVersionInfo === true;
|
|
153
|
+
const localeState = this.resolveLocaleOptions(options);
|
|
154
|
+
const documentQueryOptions = this.withLocaleOptions(this.stripDocumentListOptions(options), localeState, ['titleMl', 'summaryMl', 'descriptionMl']);
|
|
155
|
+
const versionQueryOptions = this.withLocaleOptions({
|
|
156
|
+
sort: [{ field: 'version', type: 'desc' }],
|
|
157
|
+
}, localeState, ['releaseNotesMl']) ?? {
|
|
158
|
+
sort: [{ field: 'version', type: 'desc' }],
|
|
159
|
+
};
|
|
160
|
+
return Effect.gen(this, function* () {
|
|
161
|
+
const filtered = yield* validateInput(filter, 'filter', { stage });
|
|
162
|
+
const documents = yield* this.documentRepository
|
|
163
|
+
.find({
|
|
164
|
+
matchEq: filtered,
|
|
165
|
+
options: documentQueryOptions,
|
|
166
|
+
})
|
|
167
|
+
.pipe(Effect.mapError(mapDbError({
|
|
168
|
+
stage,
|
|
169
|
+
operation: 'find',
|
|
170
|
+
factory: XfErrorFactory.notFound,
|
|
171
|
+
})));
|
|
172
|
+
if (!includeVersionInfo) {
|
|
173
|
+
return documents;
|
|
174
|
+
}
|
|
175
|
+
if (!this.documentVersionRepository) {
|
|
176
|
+
return yield* Effect.fail(XfErrorFactory.configurationError({
|
|
177
|
+
stage,
|
|
178
|
+
operation: 'listDocuments',
|
|
179
|
+
message: 'Missing dependency: documentVersionRepository',
|
|
180
|
+
}));
|
|
181
|
+
}
|
|
182
|
+
const documentsWithVersions = yield* Effect.all(documents.map((document) => this.documentVersionRepository
|
|
183
|
+
.find({
|
|
184
|
+
matchEq: { documentId: document.id },
|
|
185
|
+
options: versionQueryOptions,
|
|
186
|
+
})
|
|
187
|
+
.pipe(Effect.mapError(mapDbError({
|
|
188
|
+
stage,
|
|
189
|
+
operation: 'find',
|
|
190
|
+
factory: XfErrorFactory.notFound,
|
|
191
|
+
})), Effect.map((versions) => ({
|
|
192
|
+
...document,
|
|
193
|
+
documentVersions: versions.sort((a, b) => Number(b.version ?? 0) - Number(a.version ?? 0)),
|
|
194
|
+
})))), { concurrency: 6 });
|
|
195
|
+
return documentsWithVersions;
|
|
196
|
+
}).pipe(Effect.tapError((e) => Effect.sync(() => {
|
|
197
|
+
const info = effectErrorInfo(e);
|
|
198
|
+
this.logger?.error({ error: info.unwrapped, stage }, 'Error in listDocuments');
|
|
199
|
+
})));
|
|
200
|
+
}
|
|
201
|
+
updateDocument(id, patch) {
|
|
202
|
+
const stage = 'DocumentService::updateDocument';
|
|
203
|
+
if (!patch || Object.keys(patch).length === 0) {
|
|
204
|
+
return Effect.fail(XfErrorFactory.inputRequired({ field: 'patch', stage }));
|
|
205
|
+
}
|
|
206
|
+
return pipe(validateInput(id, 'id', { stage }), Effect.flatMap((entityId) => validateBmInputWithSchema({
|
|
207
|
+
input: patch,
|
|
208
|
+
schema: documentZodSchemaInsert.partial().strict(),
|
|
209
|
+
stage,
|
|
210
|
+
operation: 'DocumentService::updateDocument.documentZodSchemaInsert.patch',
|
|
211
|
+
field: 'patch',
|
|
212
|
+
}).pipe(Effect.map(() => entityId))), Effect.flatMap((entityId) => this.documentRepository.patchById(entityId, patch).pipe(Effect.mapError(mapDbError({ stage, operation: 'patchById', factory: XfErrorFactory.upsertFailed })))), Effect.tapError((e) => Effect.sync(() => {
|
|
213
|
+
const info = effectErrorInfo(e);
|
|
214
|
+
this.logger?.error({ error: info.unwrapped, stage }, 'Error in updateDocument');
|
|
215
|
+
})));
|
|
216
|
+
}
|
|
217
|
+
removeDocument(id) {
|
|
218
|
+
const stage = 'DocumentService::removeDocument';
|
|
219
|
+
return pipe(validateInput(id, 'id', { stage }), Effect.flatMap((entityId) => this.documentRepository.deleteById(entityId).pipe(Effect.mapError(mapDbError({ stage, operation: 'deleteById', factory: XfErrorFactory.upsertFailed })))), Effect.map(() => undefined));
|
|
220
|
+
}
|
|
221
|
+
buildDocumentIndex(documentVersionId, options) {
|
|
222
|
+
const stage = 'DocumentService::buildDocumentIndex';
|
|
223
|
+
return Effect.gen(this, function* (_) {
|
|
224
|
+
const versionId = yield* _(validateInput(documentVersionId, 'documentVersionId', { stage }));
|
|
225
|
+
const resolved = yield* _(this.resolveComposedDocument(versionId, options, stage, 'buildDocumentIndex'));
|
|
226
|
+
return this.toDocumentComposeIndex(resolved);
|
|
227
|
+
}).pipe(Effect.tapError((e) => Effect.sync(() => {
|
|
228
|
+
const info = effectErrorInfo(e);
|
|
229
|
+
this.logger?.error({ error: info.unwrapped, stage }, 'Error in buildDocumentIndex');
|
|
230
|
+
})));
|
|
231
|
+
}
|
|
232
|
+
buildPersistedDocumentIndex(input) {
|
|
233
|
+
const stage = 'DocumentService::buildPersistedDocumentIndex';
|
|
234
|
+
return Effect.gen(this, function* (_) {
|
|
235
|
+
const payload = yield* _(validateInput(input, 'input', { stage }));
|
|
236
|
+
const versionId = yield* _(validateInput(payload.documentVersionId, 'documentVersionId', { stage }));
|
|
237
|
+
const localeKey = this.resolveDocumentIndexLocaleKey(payload);
|
|
238
|
+
const buildKey = this.buildPersistedDocumentIndexBuildKey(versionId, localeKey);
|
|
239
|
+
const activeBuild = activePersistedDocumentIndexBuilds.get(buildKey);
|
|
240
|
+
if (activeBuild) {
|
|
241
|
+
return yield* _(Effect.tryPromise({
|
|
242
|
+
try: () => activeBuild,
|
|
243
|
+
catch: (error) => error,
|
|
244
|
+
}));
|
|
245
|
+
}
|
|
246
|
+
const buildPromise = Effect.runPromise(this.buildPersistedDocumentIndexForResolvedInput(payload, versionId, localeKey, stage)).finally(() => {
|
|
247
|
+
if (activePersistedDocumentIndexBuilds.get(buildKey) === buildPromise) {
|
|
248
|
+
activePersistedDocumentIndexBuilds.delete(buildKey);
|
|
249
|
+
}
|
|
250
|
+
});
|
|
251
|
+
activePersistedDocumentIndexBuilds.set(buildKey, buildPromise);
|
|
252
|
+
return yield* _(Effect.tryPromise({
|
|
253
|
+
try: () => buildPromise,
|
|
254
|
+
catch: (error) => error,
|
|
255
|
+
}));
|
|
256
|
+
}).pipe(Effect.tapError((e) => Effect.sync(() => {
|
|
257
|
+
const info = effectErrorInfo(e);
|
|
258
|
+
this.logger?.error({ error: info.unwrapped, stage }, 'Error in buildPersistedDocumentIndex');
|
|
259
|
+
})));
|
|
260
|
+
}
|
|
261
|
+
buildPersistedDocumentIndexForResolvedInput(payload, versionId, localeKey, stage) {
|
|
262
|
+
return Effect.gen(this, function* (_) {
|
|
263
|
+
const repository = yield* _(this.requireDependency(this.documentIndexEntryRepository, 'documentIndexEntryRepository', stage, 'buildPersistedDocumentIndex'));
|
|
264
|
+
const resolved = yield* _(this.resolveComposedDocument(versionId, payload, stage, 'buildPersistedDocumentIndex'));
|
|
265
|
+
const rows = yield* _(this.populatePersistedDocumentIndexEmbeddings(this.toPersistedDocumentIndexRows(resolved, localeKey), stage, 'buildPersistedDocumentIndex.embed'));
|
|
266
|
+
const existingRows = yield* _(this.listPersistedDocumentIndexRows(repository, versionId, localeKey, stage, 'buildPersistedDocumentIndex.find'));
|
|
267
|
+
yield* _(this.deletePersistedDocumentIndexRows(repository, existingRows, stage, 'buildPersistedDocumentIndex.delete'));
|
|
268
|
+
const createdRows = yield* _(this.createPersistedDocumentIndexRows(repository, rows, versionId, localeKey, stage, 'buildPersistedDocumentIndex.create'));
|
|
269
|
+
return this.toPersistedDocumentIndexSnapshot(versionId, localeKey, createdRows);
|
|
270
|
+
});
|
|
271
|
+
}
|
|
272
|
+
getPersistedDocumentIndex(input) {
|
|
273
|
+
const stage = 'DocumentService::getPersistedDocumentIndex';
|
|
274
|
+
return Effect.gen(this, function* (_) {
|
|
275
|
+
const payload = yield* _(validateInput(input, 'input', { stage }));
|
|
276
|
+
const versionId = yield* _(validateInput(payload.documentVersionId, 'documentVersionId', { stage }));
|
|
277
|
+
const localeKey = this.resolveDocumentIndexLocaleKey(payload);
|
|
278
|
+
const repository = yield* _(this.requireDependency(this.documentIndexEntryRepository, 'documentIndexEntryRepository', stage, 'getPersistedDocumentIndex'));
|
|
279
|
+
const rows = yield* _(this.listPersistedDocumentIndexRows(repository, versionId, localeKey, stage, 'getPersistedDocumentIndex.find'));
|
|
280
|
+
return this.toPersistedDocumentIndexSnapshot(versionId, localeKey, rows);
|
|
281
|
+
}).pipe(Effect.tapError((e) => Effect.sync(() => {
|
|
282
|
+
const info = effectErrorInfo(e);
|
|
283
|
+
this.logger?.error({ error: info.unwrapped, stage }, 'Error in getPersistedDocumentIndex');
|
|
284
|
+
})));
|
|
285
|
+
}
|
|
286
|
+
buildPersistedDocumentSummary(input) {
|
|
287
|
+
const stage = 'DocumentService::buildPersistedDocumentSummary';
|
|
288
|
+
return Effect.gen(this, function* (_) {
|
|
289
|
+
const payload = yield* _(validateInput(input, 'input', { stage }));
|
|
290
|
+
const versionId = yield* _(validateInput(payload.documentVersionId, 'documentVersionId', { stage }));
|
|
291
|
+
const localeKey = this.resolveDocumentIndexLocaleKey(payload);
|
|
292
|
+
const repository = yield* _(this.requireDependency(this.documentIndexEntryRepository, 'documentIndexEntryRepository', stage, 'buildPersistedDocumentSummary'));
|
|
293
|
+
yield* _(this.buildPersistedDocumentIndex(payload));
|
|
294
|
+
const rows = yield* _(this.listPersistedDocumentIndexRows(repository, versionId, localeKey, stage, 'buildPersistedDocumentSummary.find'));
|
|
295
|
+
if (rows.length === 0) {
|
|
296
|
+
return this.toPersistedDocumentSummarySnapshot(versionId, localeKey, rows);
|
|
297
|
+
}
|
|
298
|
+
const authoredDocumentSummary = yield* _(this.resolvePersistedDocumentAuthoredSummaryText(rows, localeKey, stage, 'buildPersistedDocumentSummary.resolveAuthorSummary'));
|
|
299
|
+
const summaryPatches = this.toPersistedDocumentSummaryPatches(rows, authoredDocumentSummary);
|
|
300
|
+
yield* _(Effect.all(summaryPatches.map(({ id, patch }) => repository.patchById(id, patch).pipe(Effect.mapError(mapDbError({
|
|
301
|
+
stage,
|
|
302
|
+
operation: 'documentIndexEntryRepository.patchById',
|
|
303
|
+
factory: XfErrorFactory.upsertFailed,
|
|
304
|
+
})))), { concurrency: 6 }));
|
|
305
|
+
const updatedRows = yield* _(this.listPersistedDocumentIndexRows(repository, versionId, localeKey, stage, 'buildPersistedDocumentSummary.reload'));
|
|
306
|
+
yield* _(this.refreshPersistedDocumentIndexEmbeddings(repository, updatedRows, stage, 'buildPersistedDocumentSummary.embed'));
|
|
307
|
+
return this.toPersistedDocumentSummarySnapshot(versionId, localeKey, updatedRows);
|
|
308
|
+
}).pipe(Effect.tapError((e) => Effect.sync(() => {
|
|
309
|
+
const info = effectErrorInfo(e);
|
|
310
|
+
this.logger?.error({ error: info.unwrapped, stage }, 'Error in buildPersistedDocumentSummary');
|
|
311
|
+
})));
|
|
312
|
+
}
|
|
313
|
+
getPersistedDocumentSummary(input) {
|
|
314
|
+
const stage = 'DocumentService::getPersistedDocumentSummary';
|
|
315
|
+
return Effect.gen(this, function* (_) {
|
|
316
|
+
const payload = yield* _(validateInput(input, 'input', { stage }));
|
|
317
|
+
const versionId = yield* _(validateInput(payload.documentVersionId, 'documentVersionId', { stage }));
|
|
318
|
+
const localeKey = this.resolveDocumentIndexLocaleKey(payload);
|
|
319
|
+
const repository = yield* _(this.requireDependency(this.documentIndexEntryRepository, 'documentIndexEntryRepository', stage, 'getPersistedDocumentSummary'));
|
|
320
|
+
const rows = yield* _(this.listPersistedDocumentIndexRows(repository, versionId, localeKey, stage, 'getPersistedDocumentSummary.find'));
|
|
321
|
+
return this.toPersistedDocumentSummarySnapshot(versionId, localeKey, rows);
|
|
322
|
+
}).pipe(Effect.tapError((e) => Effect.sync(() => {
|
|
323
|
+
const info = effectErrorInfo(e);
|
|
324
|
+
this.logger?.error({ error: info.unwrapped, stage }, 'Error in getPersistedDocumentSummary');
|
|
325
|
+
})));
|
|
326
|
+
}
|
|
327
|
+
searchPersistedDocumentIndex(input) {
|
|
328
|
+
const stage = 'DocumentService::searchPersistedDocumentIndex';
|
|
329
|
+
return Effect.gen(this, function* (_) {
|
|
330
|
+
const payload = yield* _(validateInput(input, 'input', { stage }));
|
|
331
|
+
const versionId = yield* _(validateInput(payload.documentVersionId, 'documentVersionId', { stage }));
|
|
332
|
+
const q = this.normalizeNonEmpty(payload.q);
|
|
333
|
+
if (!q) {
|
|
334
|
+
return yield* _(Effect.fail(XfErrorFactory.inputRequired({ field: 'q', stage })));
|
|
335
|
+
}
|
|
336
|
+
const localeKey = this.resolveDocumentIndexLocaleKey(payload);
|
|
337
|
+
const repository = yield* _(this.requireDependency(this.documentIndexEntryRepository, 'documentIndexEntryRepository', stage, 'searchPersistedDocumentIndex'));
|
|
338
|
+
const rows = yield* _(this.listPersistedDocumentIndexRows(repository, versionId, localeKey, stage, 'searchPersistedDocumentIndex.find'));
|
|
339
|
+
return yield* _(this.searchPersistedDocumentIndexRows(versionId, localeKey, q, payload.limit, payload.retrievalStrategy, rows));
|
|
340
|
+
}).pipe(Effect.tapError((e) => Effect.sync(() => {
|
|
341
|
+
const info = effectErrorInfo(e);
|
|
342
|
+
this.logger?.error({ error: info.unwrapped, stage }, 'Error in searchPersistedDocumentIndex');
|
|
343
|
+
})));
|
|
344
|
+
}
|
|
345
|
+
searchScopePersistedDocumentIndex(input) {
|
|
346
|
+
const stage = 'DocumentService::searchScopePersistedDocumentIndex';
|
|
347
|
+
return Effect.gen(this, function* (_) {
|
|
348
|
+
const payload = yield* _(validateInput(input, 'input', { stage }));
|
|
349
|
+
const scopeId = yield* _(validateInput(payload.scopeId, 'scopeId', { stage }));
|
|
350
|
+
const q = this.normalizeNonEmpty(payload.q);
|
|
351
|
+
if (!q) {
|
|
352
|
+
return yield* _(Effect.fail(XfErrorFactory.inputRequired({ field: 'q', stage })));
|
|
353
|
+
}
|
|
354
|
+
const localeKey = this.resolveDocumentIndexLocaleKey(payload);
|
|
355
|
+
const repository = yield* _(this.requireDependency(this.documentIndexEntryRepository, 'documentIndexEntryRepository', stage, 'searchScopePersistedDocumentIndex'));
|
|
356
|
+
const documents = yield* _(this.listDocuments({ scopeId }, {
|
|
357
|
+
includeVersionInfo: true,
|
|
358
|
+
locale: localeKey.locale,
|
|
359
|
+
fallbackLocale: localeKey.fallbackLocale,
|
|
360
|
+
}));
|
|
361
|
+
const retrievalStrategy = this.resolveDocumentRetrievalStrategy(payload.retrievalStrategy);
|
|
362
|
+
const limit = this.resolveDocumentIndexSearchLimit(payload.limit);
|
|
363
|
+
const autoBuiltDocumentVersionIds = [];
|
|
364
|
+
const failures = [];
|
|
365
|
+
const aggregatedHits = [];
|
|
366
|
+
let searchedDocumentCount = 0;
|
|
367
|
+
for (const document of documents) {
|
|
368
|
+
const seed = this.resolveScopeSearchDocumentSeed(document);
|
|
369
|
+
if (!seed) {
|
|
370
|
+
failures.push({
|
|
371
|
+
documentId: this.normalizeNonEmpty(document.id),
|
|
372
|
+
documentTitle: this.normalizeNonEmpty(document.title),
|
|
373
|
+
stage: 'resolve-latest-version',
|
|
374
|
+
message: 'Latest document version could not be resolved.',
|
|
375
|
+
});
|
|
376
|
+
continue;
|
|
377
|
+
}
|
|
378
|
+
let rows = yield* _(this.listPersistedDocumentIndexRows(repository, seed.documentVersionId, localeKey, stage, 'searchScopePersistedDocumentIndex.find'));
|
|
379
|
+
if (rows.length === 0) {
|
|
380
|
+
const buildAttempt = yield* _(Effect.either(this.buildPersistedDocumentIndex({
|
|
381
|
+
documentVersionId: seed.documentVersionId,
|
|
382
|
+
locale: localeKey.locale,
|
|
383
|
+
fallbackLocale: localeKey.fallbackLocale,
|
|
384
|
+
})));
|
|
385
|
+
if (buildAttempt._tag === 'Left') {
|
|
386
|
+
failures.push({
|
|
387
|
+
documentId: seed.documentId,
|
|
388
|
+
documentTitle: seed.documentTitle,
|
|
389
|
+
documentVersionId: seed.documentVersionId,
|
|
390
|
+
stage: 'build-index',
|
|
391
|
+
message: this.describeScopeSearchError(buildAttempt.left),
|
|
392
|
+
});
|
|
393
|
+
continue;
|
|
394
|
+
}
|
|
395
|
+
autoBuiltDocumentVersionIds.push(seed.documentVersionId);
|
|
396
|
+
rows = yield* _(this.listPersistedDocumentIndexRows(repository, seed.documentVersionId, localeKey, stage, 'searchScopePersistedDocumentIndex.reload'));
|
|
397
|
+
}
|
|
398
|
+
const searchResult = yield* _(Effect.either(this.searchPersistedDocumentIndexRows(seed.documentVersionId, localeKey, q, limit, retrievalStrategy, rows)));
|
|
399
|
+
if (searchResult._tag === 'Left') {
|
|
400
|
+
failures.push({
|
|
401
|
+
documentId: seed.documentId,
|
|
402
|
+
documentTitle: seed.documentTitle,
|
|
403
|
+
documentVersionId: seed.documentVersionId,
|
|
404
|
+
stage: 'search',
|
|
405
|
+
message: this.describeScopeSearchError(searchResult.left),
|
|
406
|
+
});
|
|
407
|
+
continue;
|
|
408
|
+
}
|
|
409
|
+
searchedDocumentCount += 1;
|
|
410
|
+
aggregatedHits.push(...searchResult.right.hits.map((hit) => this.toScopeDocumentSearchHit(seed, hit)));
|
|
411
|
+
}
|
|
412
|
+
const hits = [...aggregatedHits]
|
|
413
|
+
.sort((left, right) => {
|
|
414
|
+
const scoreDelta = right.score - left.score;
|
|
415
|
+
if (scoreDelta !== 0)
|
|
416
|
+
return scoreDelta;
|
|
417
|
+
const pageDelta = (left.pageNumberStart ?? Number.MAX_SAFE_INTEGER) - (right.pageNumberStart ?? Number.MAX_SAFE_INTEGER);
|
|
418
|
+
if (pageDelta !== 0)
|
|
419
|
+
return pageDelta;
|
|
420
|
+
const titleDelta = left.documentTitle.localeCompare(right.documentTitle);
|
|
421
|
+
if (titleDelta !== 0)
|
|
422
|
+
return titleDelta;
|
|
423
|
+
const breadcrumbDelta = left.breadcrumb.localeCompare(right.breadcrumb);
|
|
424
|
+
if (breadcrumbDelta !== 0)
|
|
425
|
+
return breadcrumbDelta;
|
|
426
|
+
return left.anchor.localeCompare(right.anchor);
|
|
427
|
+
})
|
|
428
|
+
.slice(0, limit);
|
|
429
|
+
return {
|
|
430
|
+
scopeId,
|
|
431
|
+
locale: localeKey.locale,
|
|
432
|
+
fallbackLocale: localeKey.fallbackLocale,
|
|
433
|
+
q,
|
|
434
|
+
hits,
|
|
435
|
+
provenance: {
|
|
436
|
+
strategy: this.toDocumentSearchStrategy(retrievalStrategy),
|
|
437
|
+
retrievalStrategy,
|
|
438
|
+
totalDocumentCount: documents.length,
|
|
439
|
+
searchedDocumentCount,
|
|
440
|
+
autoBuiltDocumentCount: autoBuiltDocumentVersionIds.length,
|
|
441
|
+
failedDocumentCount: failures.length,
|
|
442
|
+
},
|
|
443
|
+
buildReport: {
|
|
444
|
+
autoBuiltDocumentVersionIds,
|
|
445
|
+
failures,
|
|
446
|
+
},
|
|
447
|
+
};
|
|
448
|
+
}).pipe(Effect.tapError((e) => Effect.sync(() => {
|
|
449
|
+
const info = effectErrorInfo(e);
|
|
450
|
+
this.logger?.error({ error: info.unwrapped, stage }, 'Error in searchScopePersistedDocumentIndex');
|
|
451
|
+
})));
|
|
452
|
+
}
|
|
453
|
+
getDocumentAnswerPack(input) {
|
|
454
|
+
const stage = 'DocumentService::getDocumentAnswerPack';
|
|
455
|
+
return Effect.gen(this, function* (_) {
|
|
456
|
+
const payload = yield* _(validateInput(input, 'input', { stage }));
|
|
457
|
+
const versionId = yield* _(validateInput(payload.documentVersionId, 'documentVersionId', { stage }));
|
|
458
|
+
const q = this.normalizeNonEmpty(payload.q);
|
|
459
|
+
if (!q) {
|
|
460
|
+
return yield* _(Effect.fail(XfErrorFactory.inputRequired({ field: 'q', stage })));
|
|
461
|
+
}
|
|
462
|
+
const localeKey = this.resolveDocumentIndexLocaleKey(payload);
|
|
463
|
+
const repository = yield* _(this.requireDependency(this.documentIndexEntryRepository, 'documentIndexEntryRepository', stage, 'getDocumentAnswerPack'));
|
|
464
|
+
const rows = yield* _(this.listPersistedDocumentIndexRows(repository, versionId, localeKey, stage, 'getDocumentAnswerPack.find'));
|
|
465
|
+
return yield* _(this.buildDocumentAnswerPack(versionId, localeKey, q, payload.limit, payload.retrievalStrategy, rows));
|
|
466
|
+
}).pipe(Effect.tapError((e) => Effect.sync(() => {
|
|
467
|
+
const info = effectErrorInfo(e);
|
|
468
|
+
this.logger?.error({ error: info.unwrapped, stage }, 'Error in getDocumentAnswerPack');
|
|
469
|
+
})));
|
|
470
|
+
}
|
|
471
|
+
fetchComposedFragment(input) {
|
|
472
|
+
const stage = 'DocumentService::fetchComposedFragment';
|
|
473
|
+
return Effect.gen(this, function* (_) {
|
|
474
|
+
const payload = yield* _(validateInput(input, 'input', { stage }));
|
|
475
|
+
const versionId = yield* _(validateInput(payload.documentVersionId, 'documentVersionId', { stage }));
|
|
476
|
+
const resolved = yield* _(this.resolveComposedDocument(versionId, payload, stage, 'fetchComposedFragment'));
|
|
477
|
+
if (payload.pageNumber !== undefined) {
|
|
478
|
+
const pageNumber = Number(payload.pageNumber);
|
|
479
|
+
if (!Number.isInteger(pageNumber) || pageNumber <= 0) {
|
|
480
|
+
return yield* _(Effect.fail(XfErrorFactory.inputRequired({
|
|
481
|
+
field: 'pageNumber',
|
|
482
|
+
stage,
|
|
483
|
+
message: 'pageNumber must be a positive integer.',
|
|
484
|
+
})));
|
|
485
|
+
}
|
|
486
|
+
const page = resolved.pages.find((item) => item.pageNumber === pageNumber);
|
|
487
|
+
if (!page) {
|
|
488
|
+
return yield* _(Effect.fail(XfErrorFactory.notFound({
|
|
489
|
+
stage,
|
|
490
|
+
operation: 'fetchComposedFragment',
|
|
491
|
+
message: 'Composed page not found.',
|
|
492
|
+
identifier: { documentVersionId: versionId, pageNumber },
|
|
493
|
+
})));
|
|
494
|
+
}
|
|
495
|
+
const bundle = this.renderPageSourceBundle(resolved.title, page);
|
|
496
|
+
return {
|
|
497
|
+
documentVersionId: versionId,
|
|
498
|
+
kind: 'page',
|
|
499
|
+
pageNumber,
|
|
500
|
+
...bundle,
|
|
501
|
+
};
|
|
502
|
+
}
|
|
503
|
+
const targetPageVersionId = this.normalizeNonEmpty(payload.pageVersionId);
|
|
504
|
+
if (targetPageVersionId) {
|
|
505
|
+
const pageItem = resolved.items.find((item) => item.kind === 'page' && item.pageVersionId === targetPageVersionId);
|
|
506
|
+
if (!pageItem) {
|
|
507
|
+
return yield* _(Effect.fail(XfErrorFactory.notFound({
|
|
508
|
+
stage,
|
|
509
|
+
operation: 'fetchComposedFragment',
|
|
510
|
+
message: 'Page not found in composed document.',
|
|
511
|
+
identifier: { documentVersionId: versionId, pageVersionId: targetPageVersionId },
|
|
512
|
+
})));
|
|
513
|
+
}
|
|
514
|
+
const bundle = this.renderSinglePageItemSourceBundle(resolved.title, pageItem);
|
|
515
|
+
return {
|
|
516
|
+
documentVersionId: versionId,
|
|
517
|
+
kind: 'page',
|
|
518
|
+
pageVersionId: pageItem.pageVersionId,
|
|
519
|
+
...bundle,
|
|
520
|
+
};
|
|
521
|
+
}
|
|
522
|
+
const targetSectionId = this.normalizeNonEmpty(payload.sectionId);
|
|
523
|
+
if (targetSectionId) {
|
|
524
|
+
const sectionItem = resolved.items.find((item) => item.kind === 'section' && item.sectionId === targetSectionId);
|
|
525
|
+
if (!sectionItem) {
|
|
526
|
+
return yield* _(Effect.fail(XfErrorFactory.notFound({
|
|
527
|
+
stage,
|
|
528
|
+
operation: 'fetchComposedFragment',
|
|
529
|
+
message: 'Section not found in composed document.',
|
|
530
|
+
identifier: { documentVersionId: versionId, sectionId: targetSectionId },
|
|
531
|
+
})));
|
|
532
|
+
}
|
|
533
|
+
const bundle = this.renderSectionSourceBundleFromSection(resolved.title, resolved, sectionItem);
|
|
534
|
+
return {
|
|
535
|
+
documentVersionId: versionId,
|
|
536
|
+
kind: 'section',
|
|
537
|
+
sectionId: sectionItem.sectionId,
|
|
538
|
+
...bundle,
|
|
539
|
+
};
|
|
540
|
+
}
|
|
541
|
+
const bundle = this.renderDocumentSourceBundleFromResolved(resolved);
|
|
542
|
+
return {
|
|
543
|
+
documentVersionId: versionId,
|
|
544
|
+
kind: 'document',
|
|
545
|
+
...bundle,
|
|
546
|
+
};
|
|
547
|
+
}).pipe(Effect.tapError((e) => Effect.sync(() => {
|
|
548
|
+
const info = effectErrorInfo(e);
|
|
549
|
+
this.logger?.error({ error: info.unwrapped, stage }, 'Error in fetchComposedFragment');
|
|
550
|
+
})));
|
|
551
|
+
}
|
|
552
|
+
materializePublishedFragment(input) {
|
|
553
|
+
const stage = 'DocumentService::materializePublishedFragment';
|
|
554
|
+
return Effect.gen(this, function* (_) {
|
|
555
|
+
const payload = yield* _(validateInput(input, 'input', { stage }));
|
|
556
|
+
const targetDescriptor = resolveDocmanPublishTargetDescriptor(payload.target);
|
|
557
|
+
if (!targetDescriptor) {
|
|
558
|
+
return yield* _(Effect.fail(XfErrorFactory.inputRequired({
|
|
559
|
+
field: 'target',
|
|
560
|
+
stage,
|
|
561
|
+
message: `target must be one of: ${formatDocmanPublishTargets()}`,
|
|
562
|
+
})));
|
|
563
|
+
}
|
|
564
|
+
const composed = yield* _(this.fetchComposedFragment({
|
|
565
|
+
documentVersionId: payload.documentVersionId,
|
|
566
|
+
sectionId: payload.sectionId,
|
|
567
|
+
pageVersionId: payload.pageVersionId,
|
|
568
|
+
pageNumber: payload.pageNumber,
|
|
569
|
+
locale: payload.locale,
|
|
570
|
+
fallbackLocale: payload.fallbackLocale,
|
|
571
|
+
}));
|
|
572
|
+
const materialized = this.materializePublishContent(composed, targetDescriptor);
|
|
573
|
+
return {
|
|
574
|
+
...composed,
|
|
575
|
+
target: targetDescriptor.target,
|
|
576
|
+
mediaType: materialized.mediaType,
|
|
577
|
+
content: materialized.content,
|
|
578
|
+
warnings: materialized.warnings,
|
|
579
|
+
};
|
|
580
|
+
}).pipe(Effect.tapError((e) => Effect.sync(() => {
|
|
581
|
+
const info = effectErrorInfo(e);
|
|
582
|
+
this.logger?.error({ error: info.unwrapped, stage }, 'Error in materializePublishedFragment');
|
|
583
|
+
})));
|
|
584
|
+
}
|
|
585
|
+
removeDocumentSafe(id, confirmName) {
|
|
586
|
+
const stage = 'DocumentService::removeDocumentSafe';
|
|
587
|
+
return Effect.gen(this, function* (_) {
|
|
588
|
+
const entityId = yield* _(validateInput(id, 'id', { stage }));
|
|
589
|
+
const confirmation = String(confirmName ?? '').trim();
|
|
590
|
+
if (!confirmation) {
|
|
591
|
+
return yield* _(Effect.fail(XfErrorFactory.inputRequired({ field: 'confirmName', stage })));
|
|
592
|
+
}
|
|
593
|
+
const document = yield* _(this.documentRepository.findById(entityId).pipe(Effect.mapError(mapDbError({ stage, operation: 'findById', factory: XfErrorFactory.notFound }))));
|
|
594
|
+
const expected = String(document?.title ?? '').trim();
|
|
595
|
+
if (confirmation !== expected) {
|
|
596
|
+
return yield* _(Effect.fail(XfErrorFactory.upsertFailed({
|
|
597
|
+
stage,
|
|
598
|
+
operation: 'confirmName',
|
|
599
|
+
message: 'Document title confirmation mismatch.',
|
|
600
|
+
data: { expected, received: confirmation },
|
|
601
|
+
})));
|
|
602
|
+
}
|
|
603
|
+
const dependencies = yield* _(this.resolveCascadeDependencies(stage, 'removeDocumentSafe'));
|
|
604
|
+
return yield* _((deleteDocumentCascade(dependencies, entityId, stage).pipe(Effect.mapError((error) => this.normalizeCascadeError(stage, 'removeDocumentSafe.cascade', error)))));
|
|
605
|
+
}).pipe(Effect.tapError((e) => Effect.sync(() => {
|
|
606
|
+
const info = effectErrorInfo(e);
|
|
607
|
+
this.logger?.error({ error: info.unwrapped, stage }, 'Error in removeDocumentSafe');
|
|
608
|
+
})));
|
|
609
|
+
}
|
|
610
|
+
resolveComposedDocument(documentVersionId, options, stage, operation) {
|
|
611
|
+
return Effect.gen(this, function* (_) {
|
|
612
|
+
const localeState = this.resolveLocaleOptions(options);
|
|
613
|
+
const documentVersionRepository = yield* _(this.requireDependency(this.documentVersionRepository, 'documentVersionRepository', stage, operation));
|
|
614
|
+
const documentSectionLinkRepository = yield* _(this.requireDependency(this.documentSectionLinkRepository, 'documentSectionLinkRepository', stage, operation));
|
|
615
|
+
const sectionRepository = yield* _(this.requireDependency(this.sectionRepository, 'sectionRepository', stage, operation));
|
|
616
|
+
const pageVersionRepository = yield* _(this.requireDependency(this.pageVersionRepository, 'pageVersionRepository', stage, operation));
|
|
617
|
+
const documentVersionQueryOptions = this.withLocaleOptions(undefined, localeState, ['releaseNotesMl']);
|
|
618
|
+
const sectionQueryOptions = this.withLocaleOptions(undefined, localeState, ['titleMl']);
|
|
619
|
+
const pageQueryOptions = this.withLocaleOptions(undefined, localeState, ['titleMl']);
|
|
620
|
+
const pageVersionQueryOptions = this.withLocaleOptions(undefined, localeState, ['contentMl']);
|
|
621
|
+
const documentVersion = yield* _(documentVersionRepository.findById(documentVersionId, documentVersionQueryOptions).pipe(Effect.mapError(mapDbError({ stage, operation: 'documentVersionRepository.findById', factory: XfErrorFactory.notFound }))));
|
|
622
|
+
if (!documentVersion?.id) {
|
|
623
|
+
return yield* _(Effect.fail(XfErrorFactory.notFound({
|
|
624
|
+
stage,
|
|
625
|
+
operation: 'documentVersionRepository.findById',
|
|
626
|
+
message: 'Document version not found.',
|
|
627
|
+
identifier: { documentVersionId },
|
|
628
|
+
})));
|
|
629
|
+
}
|
|
630
|
+
const documentQueryOptions = this.withLocaleOptions(undefined, localeState, ['titleMl', 'summaryMl', 'descriptionMl']);
|
|
631
|
+
const document = yield* _(this.documentRepository.findById(documentVersion.documentId, documentQueryOptions).pipe(Effect.mapError(mapDbError({ stage, operation: 'documentRepository.findById', factory: XfErrorFactory.notFound }))));
|
|
632
|
+
if (!document?.id) {
|
|
633
|
+
return yield* _(Effect.fail(XfErrorFactory.notFound({
|
|
634
|
+
stage,
|
|
635
|
+
operation: 'documentRepository.findById',
|
|
636
|
+
message: 'Document not found.',
|
|
637
|
+
identifier: { documentId: documentVersion.documentId },
|
|
638
|
+
})));
|
|
639
|
+
}
|
|
640
|
+
const links = yield* _(documentSectionLinkRepository
|
|
641
|
+
.find({
|
|
642
|
+
matchEq: { documentVersionId },
|
|
643
|
+
options: { sort: [{ field: 'position', type: 'asc' }] },
|
|
644
|
+
})
|
|
645
|
+
.pipe(Effect.mapError(mapDbError({
|
|
646
|
+
stage,
|
|
647
|
+
operation: 'documentSectionLinkRepository.find',
|
|
648
|
+
factory: XfErrorFactory.notFound,
|
|
649
|
+
}))));
|
|
650
|
+
const traversed = this.traverseLinks(links, undefined, 0);
|
|
651
|
+
const items = [];
|
|
652
|
+
for (const node of traversed) {
|
|
653
|
+
const linkId = node.link.id;
|
|
654
|
+
if (!linkId)
|
|
655
|
+
continue;
|
|
656
|
+
if (node.link.kind === 'section') {
|
|
657
|
+
const sectionId = this.normalizeNonEmpty(node.link.sectionId);
|
|
658
|
+
if (!sectionId)
|
|
659
|
+
continue;
|
|
660
|
+
const section = yield* _(sectionRepository.findById(sectionId, sectionQueryOptions).pipe(Effect.mapError(mapDbError({
|
|
661
|
+
stage,
|
|
662
|
+
operation: 'sectionRepository.findById',
|
|
663
|
+
factory: XfErrorFactory.notFound,
|
|
664
|
+
}))));
|
|
665
|
+
if (!section)
|
|
666
|
+
continue;
|
|
667
|
+
items.push({
|
|
668
|
+
linkId,
|
|
669
|
+
kind: 'section',
|
|
670
|
+
sectionId,
|
|
671
|
+
sectionUid: this.normalizeNonEmpty(section.sectionUid),
|
|
672
|
+
sectionSlug: this.normalizeNonEmpty(section.slug),
|
|
673
|
+
number: node.number,
|
|
674
|
+
depth: node.depth,
|
|
675
|
+
position: node.link.position,
|
|
676
|
+
title: this.resolveSectionTitle(section, node.link.titleOverride, localeState),
|
|
677
|
+
parentLinkId: this.normalizeNonEmpty(node.link.parentLinkId),
|
|
678
|
+
titleVisible: node.link.titleVisible !== false,
|
|
679
|
+
pageBreakBefore: node.link.pageBreakBefore === true,
|
|
680
|
+
pageBreakAfter: node.link.pageBreakAfter === true,
|
|
681
|
+
directives: node.link.directives,
|
|
682
|
+
});
|
|
683
|
+
continue;
|
|
684
|
+
}
|
|
685
|
+
if (node.link.kind === 'page') {
|
|
686
|
+
const pageVersionId = this.normalizeNonEmpty(node.link.pageVersionId);
|
|
687
|
+
if (!pageVersionId)
|
|
688
|
+
continue;
|
|
689
|
+
const pageVersion = yield* _(pageVersionRepository.findById(pageVersionId, pageVersionQueryOptions).pipe(Effect.mapError(mapDbError({
|
|
690
|
+
stage,
|
|
691
|
+
operation: 'pageVersionRepository.findById',
|
|
692
|
+
factory: XfErrorFactory.notFound,
|
|
693
|
+
}))));
|
|
694
|
+
if (!pageVersion?.id)
|
|
695
|
+
continue;
|
|
696
|
+
const resolvedPageSource = yield* _(this.resolveComposePageSource(pageVersion, localeState, stage, operation));
|
|
697
|
+
let page = null;
|
|
698
|
+
if (this.pageRepository) {
|
|
699
|
+
page = yield* _(this.pageRepository.findById(pageVersion.pageId, pageQueryOptions).pipe(Effect.mapError(mapDbError({
|
|
700
|
+
stage,
|
|
701
|
+
operation: 'pageRepository.findById',
|
|
702
|
+
factory: XfErrorFactory.notFound,
|
|
703
|
+
}))));
|
|
704
|
+
}
|
|
705
|
+
items.push({
|
|
706
|
+
linkId,
|
|
707
|
+
kind: 'page',
|
|
708
|
+
pageVersionId,
|
|
709
|
+
pageId: pageVersion.pageId,
|
|
710
|
+
pageUid: this.normalizeNonEmpty(page?.pageUid),
|
|
711
|
+
format: resolvedPageSource.format,
|
|
712
|
+
modulePreamble: resolvedPageSource.modulePreamble,
|
|
713
|
+
number: node.number,
|
|
714
|
+
depth: node.depth,
|
|
715
|
+
position: node.link.position,
|
|
716
|
+
title: this.resolvePageTitle(page, pageVersion, node.link.titleOverride, localeState),
|
|
717
|
+
parentLinkId: this.normalizeNonEmpty(node.link.parentLinkId),
|
|
718
|
+
titleVisible: node.link.titleVisible !== false,
|
|
719
|
+
pageBreakBefore: node.link.pageBreakBefore === true,
|
|
720
|
+
pageBreakAfter: node.link.pageBreakAfter === true,
|
|
721
|
+
directives: node.link.directives ?? pageVersion.directives,
|
|
722
|
+
contentParts: this.splitContentByPageBreakMarkers(resolvedPageSource.content),
|
|
723
|
+
assetRefs: resolvedPageSource.assets,
|
|
724
|
+
});
|
|
725
|
+
}
|
|
726
|
+
}
|
|
727
|
+
return {
|
|
728
|
+
document: document,
|
|
729
|
+
documentVersion: documentVersion,
|
|
730
|
+
title: this.resolveDocumentTitle(document, documentVersion, localeState),
|
|
731
|
+
documentReleaseNotes: this.resolveVersionReleaseNotes(documentVersion, localeState),
|
|
732
|
+
items,
|
|
733
|
+
pages: this.composeDynamicPages(items),
|
|
734
|
+
};
|
|
735
|
+
});
|
|
736
|
+
}
|
|
737
|
+
composeDynamicPages(items) {
|
|
738
|
+
const pages = [this.createEmptyPage(1)];
|
|
739
|
+
let currentPage = pages[0];
|
|
740
|
+
const hasPageContent = (page) => page.chunks.length > 0;
|
|
741
|
+
const startNewPage = (onlyIfCurrentHasContent = true) => {
|
|
742
|
+
if (onlyIfCurrentHasContent && !hasPageContent(currentPage))
|
|
743
|
+
return;
|
|
744
|
+
currentPage = this.createEmptyPage(pages.length + 1);
|
|
745
|
+
pages.push(currentPage);
|
|
746
|
+
};
|
|
747
|
+
for (const item of items) {
|
|
748
|
+
if (item.pageBreakBefore) {
|
|
749
|
+
startNewPage(true);
|
|
750
|
+
}
|
|
751
|
+
this.pushUnique(currentPage.itemNumbers, item.number);
|
|
752
|
+
if (item.kind !== 'page') {
|
|
753
|
+
if (item.pageBreakAfter) {
|
|
754
|
+
startNewPage(true);
|
|
755
|
+
}
|
|
756
|
+
continue;
|
|
757
|
+
}
|
|
758
|
+
const parts = item.contentParts.length > 0 ? item.contentParts : [''];
|
|
759
|
+
for (let i = 0; i < parts.length; i++) {
|
|
760
|
+
const content = parts[i] ?? '';
|
|
761
|
+
this.pushUnique(currentPage.itemNumbers, item.number);
|
|
762
|
+
this.pushUnique(currentPage.pageVersionIds, item.pageVersionId);
|
|
763
|
+
currentPage.chunks.push({
|
|
764
|
+
linkId: item.linkId,
|
|
765
|
+
number: item.number,
|
|
766
|
+
depth: item.depth,
|
|
767
|
+
title: item.title,
|
|
768
|
+
titleVisible: item.titleVisible,
|
|
769
|
+
pageVersionId: item.pageVersionId,
|
|
770
|
+
pageId: item.pageId,
|
|
771
|
+
format: item.format,
|
|
772
|
+
modulePreamble: item.modulePreamble,
|
|
773
|
+
content,
|
|
774
|
+
assets: item.assetRefs,
|
|
775
|
+
chunkIndex: i + 1,
|
|
776
|
+
chunkCount: parts.length,
|
|
777
|
+
});
|
|
778
|
+
if (i < parts.length - 1) {
|
|
779
|
+
startNewPage(true);
|
|
780
|
+
}
|
|
781
|
+
}
|
|
782
|
+
if (item.pageBreakAfter) {
|
|
783
|
+
startNewPage(true);
|
|
784
|
+
}
|
|
785
|
+
}
|
|
786
|
+
if (pages.length > 1 && !hasPageContent(pages[pages.length - 1])) {
|
|
787
|
+
pages.pop();
|
|
788
|
+
}
|
|
789
|
+
return pages.map((page, index) => ({
|
|
790
|
+
...page,
|
|
791
|
+
pageNumber: index + 1,
|
|
792
|
+
...reduceDocmanComposeFormats(page.chunks.map((chunk) => chunk.format)),
|
|
793
|
+
modulePreambles: this.uniqueModulePreambles(page.chunks.map((chunk) => chunk.modulePreamble)),
|
|
794
|
+
assets: this.uniqueAssetRefs(page.chunks.flatMap((chunk) => chunk.assets)),
|
|
795
|
+
}));
|
|
796
|
+
}
|
|
797
|
+
createEmptyPage(pageNumber) {
|
|
798
|
+
return {
|
|
799
|
+
pageNumber,
|
|
800
|
+
format: 'md',
|
|
801
|
+
formats: ['md'],
|
|
802
|
+
itemNumbers: [],
|
|
803
|
+
pageVersionIds: [],
|
|
804
|
+
chunks: [],
|
|
805
|
+
modulePreambles: [],
|
|
806
|
+
assets: [],
|
|
807
|
+
};
|
|
808
|
+
}
|
|
809
|
+
toDocumentComposeIndex(resolved) {
|
|
810
|
+
const items = resolved.items.map((item) => {
|
|
811
|
+
if (item.kind === 'section') {
|
|
812
|
+
return {
|
|
813
|
+
kind: 'section',
|
|
814
|
+
linkId: item.linkId,
|
|
815
|
+
sectionId: item.sectionId,
|
|
816
|
+
number: item.number,
|
|
817
|
+
depth: item.depth,
|
|
818
|
+
position: item.position,
|
|
819
|
+
title: item.title,
|
|
820
|
+
pageBreakBefore: item.pageBreakBefore,
|
|
821
|
+
pageBreakAfter: item.pageBreakAfter,
|
|
822
|
+
parentLinkId: item.parentLinkId,
|
|
823
|
+
directives: item.directives,
|
|
824
|
+
};
|
|
825
|
+
}
|
|
826
|
+
return {
|
|
827
|
+
kind: 'page',
|
|
828
|
+
linkId: item.linkId,
|
|
829
|
+
pageVersionId: item.pageVersionId,
|
|
830
|
+
pageId: item.pageId,
|
|
831
|
+
format: item.format,
|
|
832
|
+
number: item.number,
|
|
833
|
+
depth: item.depth,
|
|
834
|
+
position: item.position,
|
|
835
|
+
title: item.title,
|
|
836
|
+
titleVisible: item.titleVisible,
|
|
837
|
+
pageBreakBefore: item.pageBreakBefore,
|
|
838
|
+
pageBreakAfter: item.pageBreakAfter,
|
|
839
|
+
parentLinkId: item.parentLinkId,
|
|
840
|
+
directives: item.directives,
|
|
841
|
+
};
|
|
842
|
+
});
|
|
843
|
+
return {
|
|
844
|
+
documentId: resolved.document.id,
|
|
845
|
+
documentVersionId: resolved.documentVersion.id,
|
|
846
|
+
title: resolved.title,
|
|
847
|
+
items,
|
|
848
|
+
pages: resolved.pages.map((page) => ({
|
|
849
|
+
pageNumber: page.pageNumber,
|
|
850
|
+
format: page.format,
|
|
851
|
+
formats: [...page.formats],
|
|
852
|
+
itemNumbers: [...page.itemNumbers],
|
|
853
|
+
pageVersionIds: [...page.pageVersionIds],
|
|
854
|
+
})),
|
|
855
|
+
};
|
|
856
|
+
}
|
|
857
|
+
resolveDocumentIndexLocaleKey(options) {
|
|
858
|
+
const localeState = this.resolveLocaleOptions(options);
|
|
859
|
+
const locale = normalizeDocmanDocumentIndexLocale(localeState.locale);
|
|
860
|
+
const fallbackLocale = normalizeDocmanDocumentIndexLocale(localeState.fallbackLocale);
|
|
861
|
+
return {
|
|
862
|
+
...(locale ? { locale } : {}),
|
|
863
|
+
...(fallbackLocale ? { fallbackLocale } : {}),
|
|
864
|
+
};
|
|
865
|
+
}
|
|
866
|
+
listPersistedDocumentIndexRows(repository, documentVersionId, localeKey, stage, operation) {
|
|
867
|
+
return repository
|
|
868
|
+
.find({
|
|
869
|
+
matchEq: {
|
|
870
|
+
documentVersionId,
|
|
871
|
+
locale: localeKey.locale ?? '',
|
|
872
|
+
fallbackLocale: localeKey.fallbackLocale ?? '',
|
|
873
|
+
},
|
|
874
|
+
options: {
|
|
875
|
+
sort: [
|
|
876
|
+
{ field: 'sortOrder', type: 'asc' },
|
|
877
|
+
{ field: 'position', type: 'asc' },
|
|
878
|
+
],
|
|
879
|
+
},
|
|
880
|
+
})
|
|
881
|
+
.pipe(Effect.map((rows) => [...rows].sort((left, right) => {
|
|
882
|
+
const sortDelta = Number(left.sortOrder ?? 0) - Number(right.sortOrder ?? 0);
|
|
883
|
+
if (sortDelta !== 0)
|
|
884
|
+
return sortDelta;
|
|
885
|
+
return String(left.id ?? '').localeCompare(String(right.id ?? ''));
|
|
886
|
+
})), Effect.mapError(mapDbError({
|
|
887
|
+
stage,
|
|
888
|
+
operation,
|
|
889
|
+
factory: XfErrorFactory.notFound,
|
|
890
|
+
})));
|
|
891
|
+
}
|
|
892
|
+
deletePersistedDocumentIndexRows(repository, rows, stage, operation) {
|
|
893
|
+
if (!rows.length)
|
|
894
|
+
return Effect.succeed(undefined);
|
|
895
|
+
return Effect.all(rows
|
|
896
|
+
.map((row) => this.normalizeNonEmpty(row.id))
|
|
897
|
+
.filter((id) => Boolean(id))
|
|
898
|
+
.map((id) => repository.deleteById(id).pipe(Effect.mapError(mapDbError({
|
|
899
|
+
stage,
|
|
900
|
+
operation,
|
|
901
|
+
factory: XfErrorFactory.upsertFailed,
|
|
902
|
+
})))), { concurrency: 6 }).pipe(Effect.asVoid);
|
|
903
|
+
}
|
|
904
|
+
createPersistedDocumentIndexRows(repository, rows, documentVersionId, localeKey, stage, operation) {
|
|
905
|
+
if (rows.length === 0)
|
|
906
|
+
return Effect.succeed([]);
|
|
907
|
+
const createRows = Effect.all(rows.map((row) => repository.create(row).pipe(Effect.mapError(mapDbError({
|
|
908
|
+
stage,
|
|
909
|
+
operation: 'documentIndexEntryRepository.create',
|
|
910
|
+
factory: XfErrorFactory.createFailed,
|
|
911
|
+
})))), { concurrency: 6 });
|
|
912
|
+
return createRows.pipe(Effect.catchAll((error) => this.listPersistedDocumentIndexRows(repository, documentVersionId, localeKey, stage, `${operation}.reload`).pipe(Effect.flatMap((currentRows) => this.matchesPersistedDocumentIndexRows(rows, currentRows)
|
|
913
|
+
? Effect.succeed(currentRows)
|
|
914
|
+
: Effect.fail(error)), Effect.catchAll(() => Effect.fail(error)))));
|
|
915
|
+
}
|
|
916
|
+
matchesPersistedDocumentIndexRows(expectedRows, actualRows) {
|
|
917
|
+
if (expectedRows.length !== actualRows.length)
|
|
918
|
+
return false;
|
|
919
|
+
const actualByKey = new Map(actualRows.map((row) => [this.persistedDocumentIndexRowKey(row), row]));
|
|
920
|
+
for (const expectedRow of expectedRows) {
|
|
921
|
+
const actualRow = actualByKey.get(this.persistedDocumentIndexRowKey(expectedRow));
|
|
922
|
+
if (!actualRow || !this.matchesPersistedDocumentIndexRow(expectedRow, actualRow)) {
|
|
923
|
+
return false;
|
|
924
|
+
}
|
|
925
|
+
}
|
|
926
|
+
return true;
|
|
927
|
+
}
|
|
928
|
+
matchesPersistedDocumentIndexRow(expectedRow, actualRow) {
|
|
929
|
+
const fields = [
|
|
930
|
+
'documentVersionId',
|
|
931
|
+
'documentId',
|
|
932
|
+
'locale',
|
|
933
|
+
'fallbackLocale',
|
|
934
|
+
'itemKind',
|
|
935
|
+
'sortOrder',
|
|
936
|
+
'buildFingerprint',
|
|
937
|
+
'linkId',
|
|
938
|
+
'parentLinkId',
|
|
939
|
+
'anchor',
|
|
940
|
+
'parentAnchor',
|
|
941
|
+
'number',
|
|
942
|
+
'depth',
|
|
943
|
+
'position',
|
|
944
|
+
'title',
|
|
945
|
+
'breadcrumb',
|
|
946
|
+
'titleVisible',
|
|
947
|
+
'pageBreakBefore',
|
|
948
|
+
'pageBreakAfter',
|
|
949
|
+
'sectionId',
|
|
950
|
+
'sectionUid',
|
|
951
|
+
'sectionSlug',
|
|
952
|
+
'pageId',
|
|
953
|
+
'pageUid',
|
|
954
|
+
'pageVersionId',
|
|
955
|
+
'format',
|
|
956
|
+
'pageNumberStart',
|
|
957
|
+
'pageNumberEnd',
|
|
958
|
+
'bodyText',
|
|
959
|
+
'searchText',
|
|
960
|
+
];
|
|
961
|
+
return fields.every((field) => this.normalizePersistedDocumentIndexComparableValue(expectedRow[field]) ===
|
|
962
|
+
this.normalizePersistedDocumentIndexComparableValue(actualRow[field]));
|
|
963
|
+
}
|
|
964
|
+
persistedDocumentIndexRowKey(row) {
|
|
965
|
+
return `${Number(row.sortOrder ?? -1)}::${this.normalizeNonEmpty(row.anchor) ?? ''}`;
|
|
966
|
+
}
|
|
967
|
+
normalizePersistedDocumentIndexComparableValue(value) {
|
|
968
|
+
if (value === null || value === undefined)
|
|
969
|
+
return '';
|
|
970
|
+
if (typeof value === 'boolean')
|
|
971
|
+
return value ? 'true' : 'false';
|
|
972
|
+
return String(value);
|
|
973
|
+
}
|
|
974
|
+
buildPersistedDocumentIndexBuildKey(documentVersionId, localeKey) {
|
|
975
|
+
return `${documentVersionId}::${localeKey.locale ?? ''}::${localeKey.fallbackLocale ?? ''}`;
|
|
976
|
+
}
|
|
977
|
+
toPersistedDocumentIndexRows(resolved, localeKey) {
|
|
978
|
+
const pageRangesByLinkId = this.buildPersistedDocumentPageRanges(resolved.pages);
|
|
979
|
+
const fingerprint = buildDocmanDocumentIndexFingerprint({
|
|
980
|
+
documentVersionId: resolved.documentVersion.id,
|
|
981
|
+
title: resolved.title,
|
|
982
|
+
locale: localeKey.locale ?? '',
|
|
983
|
+
fallbackLocale: localeKey.fallbackLocale ?? '',
|
|
984
|
+
items: resolved.items.map((item) => item.kind === 'section'
|
|
985
|
+
? {
|
|
986
|
+
kind: item.kind,
|
|
987
|
+
linkId: item.linkId,
|
|
988
|
+
parentLinkId: item.parentLinkId,
|
|
989
|
+
number: item.number,
|
|
990
|
+
depth: item.depth,
|
|
991
|
+
position: item.position,
|
|
992
|
+
title: item.title,
|
|
993
|
+
sectionId: item.sectionId,
|
|
994
|
+
sectionUid: item.sectionUid,
|
|
995
|
+
sectionSlug: item.sectionSlug,
|
|
996
|
+
}
|
|
997
|
+
: {
|
|
998
|
+
kind: item.kind,
|
|
999
|
+
linkId: item.linkId,
|
|
1000
|
+
parentLinkId: item.parentLinkId,
|
|
1001
|
+
number: item.number,
|
|
1002
|
+
depth: item.depth,
|
|
1003
|
+
position: item.position,
|
|
1004
|
+
title: item.title,
|
|
1005
|
+
pageId: item.pageId,
|
|
1006
|
+
pageUid: item.pageUid,
|
|
1007
|
+
pageVersionId: item.pageVersionId,
|
|
1008
|
+
format: item.format,
|
|
1009
|
+
content: item.contentParts,
|
|
1010
|
+
assets: item.assetRefs.map((assetRef) => assetRef.assetVersionId),
|
|
1011
|
+
}),
|
|
1012
|
+
pages: resolved.pages.map((page) => ({
|
|
1013
|
+
pageNumber: page.pageNumber,
|
|
1014
|
+
linkIds: page.chunks.map((chunk) => chunk.linkId),
|
|
1015
|
+
})),
|
|
1016
|
+
});
|
|
1017
|
+
const documentAnchor = buildDocmanDocumentAnchor(resolved.document.documentUid ?? resolved.document.id);
|
|
1018
|
+
const anchorByLinkId = new Map();
|
|
1019
|
+
const breadcrumbByLinkId = new Map();
|
|
1020
|
+
const rows = [];
|
|
1021
|
+
rows.push({
|
|
1022
|
+
documentVersionId: resolved.documentVersion.id,
|
|
1023
|
+
documentId: resolved.document.id,
|
|
1024
|
+
locale: localeKey.locale ?? '',
|
|
1025
|
+
fallbackLocale: localeKey.fallbackLocale ?? '',
|
|
1026
|
+
itemKind: 'document',
|
|
1027
|
+
sortOrder: 0,
|
|
1028
|
+
buildFingerprint: fingerprint,
|
|
1029
|
+
anchor: documentAnchor,
|
|
1030
|
+
depth: 0,
|
|
1031
|
+
position: 0,
|
|
1032
|
+
title: resolved.title,
|
|
1033
|
+
breadcrumb: resolved.title,
|
|
1034
|
+
titleVisible: true,
|
|
1035
|
+
pageBreakBefore: false,
|
|
1036
|
+
pageBreakAfter: false,
|
|
1037
|
+
createdBy: DOCMAN_DOCUMENT_INDEX_BUILD_ACTOR,
|
|
1038
|
+
updatedBy: DOCMAN_DOCUMENT_INDEX_BUILD_ACTOR,
|
|
1039
|
+
searchText: this.normalizeDocumentIndexSearchText([resolved.title, this.normalizeNonEmpty(resolved.documentReleaseNotes)].filter(Boolean).join('\n')),
|
|
1040
|
+
});
|
|
1041
|
+
for (const [index, item] of resolved.items.entries()) {
|
|
1042
|
+
const parentAnchor = item.parentLinkId
|
|
1043
|
+
? anchorByLinkId.get(item.parentLinkId) ?? documentAnchor
|
|
1044
|
+
: documentAnchor;
|
|
1045
|
+
const breadcrumb = this.buildDocumentIndexBreadcrumb(item.title, item.parentLinkId, breadcrumbByLinkId, resolved.title);
|
|
1046
|
+
if (item.kind === 'section') {
|
|
1047
|
+
const anchor = buildDocmanSectionAnchor(item.sectionSlug ?? item.sectionUid ?? item.sectionId, item.linkId);
|
|
1048
|
+
anchorByLinkId.set(item.linkId, anchor);
|
|
1049
|
+
breadcrumbByLinkId.set(item.linkId, breadcrumb);
|
|
1050
|
+
rows.push({
|
|
1051
|
+
documentVersionId: resolved.documentVersion.id,
|
|
1052
|
+
documentId: resolved.document.id,
|
|
1053
|
+
locale: localeKey.locale ?? '',
|
|
1054
|
+
fallbackLocale: localeKey.fallbackLocale ?? '',
|
|
1055
|
+
itemKind: 'section',
|
|
1056
|
+
sortOrder: index + 1,
|
|
1057
|
+
buildFingerprint: fingerprint,
|
|
1058
|
+
linkId: item.linkId,
|
|
1059
|
+
parentLinkId: item.parentLinkId,
|
|
1060
|
+
anchor,
|
|
1061
|
+
parentAnchor,
|
|
1062
|
+
number: item.number,
|
|
1063
|
+
depth: item.depth,
|
|
1064
|
+
position: item.position,
|
|
1065
|
+
title: item.title,
|
|
1066
|
+
breadcrumb,
|
|
1067
|
+
titleVisible: item.titleVisible,
|
|
1068
|
+
pageBreakBefore: item.pageBreakBefore,
|
|
1069
|
+
pageBreakAfter: item.pageBreakAfter,
|
|
1070
|
+
sectionId: item.sectionId,
|
|
1071
|
+
sectionUid: item.sectionUid,
|
|
1072
|
+
sectionSlug: item.sectionSlug,
|
|
1073
|
+
createdBy: DOCMAN_DOCUMENT_INDEX_BUILD_ACTOR,
|
|
1074
|
+
updatedBy: DOCMAN_DOCUMENT_INDEX_BUILD_ACTOR,
|
|
1075
|
+
searchText: this.normalizeDocumentIndexSearchText([item.number, item.title, breadcrumb].filter(Boolean).join('\n')),
|
|
1076
|
+
});
|
|
1077
|
+
continue;
|
|
1078
|
+
}
|
|
1079
|
+
const anchor = buildDocmanPageAnchor(item.pageUid ?? item.pageId, item.linkId);
|
|
1080
|
+
const pageRange = pageRangesByLinkId.get(item.linkId);
|
|
1081
|
+
const bodyText = this.extractDocumentIndexBodyText(item);
|
|
1082
|
+
anchorByLinkId.set(item.linkId, anchor);
|
|
1083
|
+
breadcrumbByLinkId.set(item.linkId, breadcrumb);
|
|
1084
|
+
rows.push({
|
|
1085
|
+
documentVersionId: resolved.documentVersion.id,
|
|
1086
|
+
documentId: resolved.document.id,
|
|
1087
|
+
locale: localeKey.locale ?? '',
|
|
1088
|
+
fallbackLocale: localeKey.fallbackLocale ?? '',
|
|
1089
|
+
itemKind: 'page',
|
|
1090
|
+
sortOrder: index + 1,
|
|
1091
|
+
buildFingerprint: fingerprint,
|
|
1092
|
+
linkId: item.linkId,
|
|
1093
|
+
parentLinkId: item.parentLinkId,
|
|
1094
|
+
anchor,
|
|
1095
|
+
parentAnchor,
|
|
1096
|
+
number: item.number,
|
|
1097
|
+
depth: item.depth,
|
|
1098
|
+
position: item.position,
|
|
1099
|
+
title: item.title,
|
|
1100
|
+
breadcrumb,
|
|
1101
|
+
titleVisible: item.titleVisible,
|
|
1102
|
+
pageBreakBefore: item.pageBreakBefore,
|
|
1103
|
+
pageBreakAfter: item.pageBreakAfter,
|
|
1104
|
+
pageId: item.pageId,
|
|
1105
|
+
pageUid: item.pageUid,
|
|
1106
|
+
pageVersionId: item.pageVersionId,
|
|
1107
|
+
format: item.format,
|
|
1108
|
+
pageNumberStart: pageRange?.start,
|
|
1109
|
+
pageNumberEnd: pageRange?.end,
|
|
1110
|
+
bodyText,
|
|
1111
|
+
searchText: this.normalizeDocumentIndexSearchText([item.number, item.title, breadcrumb, bodyText].filter(Boolean).join('\n')),
|
|
1112
|
+
createdBy: DOCMAN_DOCUMENT_INDEX_BUILD_ACTOR,
|
|
1113
|
+
updatedBy: DOCMAN_DOCUMENT_INDEX_BUILD_ACTOR,
|
|
1114
|
+
});
|
|
1115
|
+
}
|
|
1116
|
+
return rows;
|
|
1117
|
+
}
|
|
1118
|
+
toPersistedDocumentIndexSnapshot(documentVersionId, localeKey, rows) {
|
|
1119
|
+
const sorted = [...rows].sort((left, right) => {
|
|
1120
|
+
const sortDelta = Number(left.sortOrder ?? 0) - Number(right.sortOrder ?? 0);
|
|
1121
|
+
if (sortDelta !== 0)
|
|
1122
|
+
return sortDelta;
|
|
1123
|
+
return String(left.id ?? '').localeCompare(String(right.id ?? ''));
|
|
1124
|
+
});
|
|
1125
|
+
const documentRow = sorted.find((row) => row.itemKind === 'document');
|
|
1126
|
+
const entries = sorted
|
|
1127
|
+
.filter((row) => row.itemKind === 'section' || row.itemKind === 'page')
|
|
1128
|
+
.map((row) => this.toPersistedDocumentIndexSnapshotEntry(row));
|
|
1129
|
+
return {
|
|
1130
|
+
documentId: this.normalizeNonEmpty(documentRow?.documentId) ?? this.normalizeNonEmpty(sorted[0]?.documentId),
|
|
1131
|
+
documentVersionId,
|
|
1132
|
+
title: this.normalizeNonEmpty(documentRow?.title),
|
|
1133
|
+
locale: this.normalizeNonEmpty(documentRow?.locale) ?? localeKey.locale,
|
|
1134
|
+
fallbackLocale: this.normalizeNonEmpty(documentRow?.fallbackLocale) ?? localeKey.fallbackLocale,
|
|
1135
|
+
built: sorted.length > 0,
|
|
1136
|
+
buildFingerprint: this.normalizeNonEmpty(documentRow?.buildFingerprint) ??
|
|
1137
|
+
this.normalizeNonEmpty(sorted[0]?.buildFingerprint),
|
|
1138
|
+
documentAnchor: this.normalizeNonEmpty(documentRow?.anchor),
|
|
1139
|
+
entries,
|
|
1140
|
+
counts: {
|
|
1141
|
+
sections: entries.filter((entry) => entry.itemKind === 'section').length,
|
|
1142
|
+
pages: entries.filter((entry) => entry.itemKind === 'page').length,
|
|
1143
|
+
},
|
|
1144
|
+
};
|
|
1145
|
+
}
|
|
1146
|
+
toPersistedDocumentSummarySnapshot(documentVersionId, localeKey, rows) {
|
|
1147
|
+
const sorted = [...rows].sort((left, right) => {
|
|
1148
|
+
const sortDelta = Number(left.sortOrder ?? 0) - Number(right.sortOrder ?? 0);
|
|
1149
|
+
if (sortDelta !== 0)
|
|
1150
|
+
return sortDelta;
|
|
1151
|
+
return String(left.id ?? '').localeCompare(String(right.id ?? ''));
|
|
1152
|
+
});
|
|
1153
|
+
const documentRow = sorted.find((row) => row.itemKind === 'document');
|
|
1154
|
+
const entries = sorted
|
|
1155
|
+
.filter((row) => Boolean(this.normalizeNonEmpty(row.summaryText)))
|
|
1156
|
+
.map((row) => this.toPersistedDocumentSummarySnapshotEntry(row));
|
|
1157
|
+
return {
|
|
1158
|
+
documentId: this.normalizeNonEmpty(documentRow?.documentId) ?? this.normalizeNonEmpty(sorted[0]?.documentId),
|
|
1159
|
+
documentVersionId,
|
|
1160
|
+
title: this.normalizeNonEmpty(documentRow?.title),
|
|
1161
|
+
locale: this.normalizeNonEmpty(documentRow?.locale) ?? localeKey.locale,
|
|
1162
|
+
fallbackLocale: this.normalizeNonEmpty(documentRow?.fallbackLocale) ?? localeKey.fallbackLocale,
|
|
1163
|
+
built: entries.length > 0,
|
|
1164
|
+
buildFingerprint: this.normalizeNonEmpty(documentRow?.buildFingerprint) ??
|
|
1165
|
+
this.normalizeNonEmpty(sorted[0]?.buildFingerprint),
|
|
1166
|
+
documentAnchor: this.normalizeNonEmpty(documentRow?.anchor),
|
|
1167
|
+
entries,
|
|
1168
|
+
counts: {
|
|
1169
|
+
documents: entries.filter((entry) => entry.itemKind === 'document').length,
|
|
1170
|
+
sections: entries.filter((entry) => entry.itemKind === 'section').length,
|
|
1171
|
+
pages: entries.filter((entry) => entry.itemKind === 'page').length,
|
|
1172
|
+
},
|
|
1173
|
+
};
|
|
1174
|
+
}
|
|
1175
|
+
toPersistedDocumentIndexSnapshotEntry(row) {
|
|
1176
|
+
return {
|
|
1177
|
+
itemKind: row.itemKind === 'section' ? 'section' : 'page',
|
|
1178
|
+
linkId: this.normalizeNonEmpty(row.linkId),
|
|
1179
|
+
parentLinkId: this.normalizeNonEmpty(row.parentLinkId),
|
|
1180
|
+
anchor: row.anchor,
|
|
1181
|
+
parentAnchor: this.normalizeNonEmpty(row.parentAnchor),
|
|
1182
|
+
number: this.normalizeNonEmpty(row.number),
|
|
1183
|
+
depth: Number(row.depth ?? 0),
|
|
1184
|
+
position: Number(row.position ?? 0),
|
|
1185
|
+
title: row.title,
|
|
1186
|
+
breadcrumb: row.breadcrumb,
|
|
1187
|
+
titleVisible: this.toDocumentIndexBoolean(row.titleVisible),
|
|
1188
|
+
pageBreakBefore: this.toDocumentIndexBoolean(row.pageBreakBefore),
|
|
1189
|
+
pageBreakAfter: this.toDocumentIndexBoolean(row.pageBreakAfter),
|
|
1190
|
+
sectionId: this.normalizeNonEmpty(row.sectionId),
|
|
1191
|
+
sectionUid: this.normalizeNonEmpty(row.sectionUid),
|
|
1192
|
+
sectionSlug: this.normalizeNonEmpty(row.sectionSlug),
|
|
1193
|
+
pageId: this.normalizeNonEmpty(row.pageId),
|
|
1194
|
+
pageUid: this.normalizeNonEmpty(row.pageUid),
|
|
1195
|
+
pageVersionId: this.normalizeNonEmpty(row.pageVersionId),
|
|
1196
|
+
format: row.format,
|
|
1197
|
+
pageNumberStart: Number.isInteger(Number(row.pageNumberStart)) ? Number(row.pageNumberStart) : undefined,
|
|
1198
|
+
pageNumberEnd: Number.isInteger(Number(row.pageNumberEnd)) ? Number(row.pageNumberEnd) : undefined,
|
|
1199
|
+
};
|
|
1200
|
+
}
|
|
1201
|
+
toPersistedDocumentSummarySnapshotEntry(row) {
|
|
1202
|
+
const itemKind = row.itemKind === 'document'
|
|
1203
|
+
? 'document'
|
|
1204
|
+
: row.itemKind === 'section'
|
|
1205
|
+
? 'section'
|
|
1206
|
+
: 'page';
|
|
1207
|
+
return {
|
|
1208
|
+
itemKind,
|
|
1209
|
+
linkId: this.normalizeNonEmpty(row.linkId),
|
|
1210
|
+
parentLinkId: this.normalizeNonEmpty(row.parentLinkId),
|
|
1211
|
+
anchor: row.anchor,
|
|
1212
|
+
parentAnchor: this.normalizeNonEmpty(row.parentAnchor),
|
|
1213
|
+
number: this.normalizeNonEmpty(row.number),
|
|
1214
|
+
depth: Number(row.depth ?? 0),
|
|
1215
|
+
position: Number(row.position ?? 0),
|
|
1216
|
+
title: row.title,
|
|
1217
|
+
breadcrumb: row.breadcrumb,
|
|
1218
|
+
titleVisible: this.toDocumentIndexBoolean(row.titleVisible),
|
|
1219
|
+
pageBreakBefore: this.toDocumentIndexBoolean(row.pageBreakBefore),
|
|
1220
|
+
pageBreakAfter: this.toDocumentIndexBoolean(row.pageBreakAfter),
|
|
1221
|
+
sectionId: this.normalizeNonEmpty(row.sectionId),
|
|
1222
|
+
sectionUid: this.normalizeNonEmpty(row.sectionUid),
|
|
1223
|
+
sectionSlug: this.normalizeNonEmpty(row.sectionSlug),
|
|
1224
|
+
pageId: this.normalizeNonEmpty(row.pageId),
|
|
1225
|
+
pageUid: this.normalizeNonEmpty(row.pageUid),
|
|
1226
|
+
pageVersionId: this.normalizeNonEmpty(row.pageVersionId),
|
|
1227
|
+
format: row.format,
|
|
1228
|
+
pageNumberStart: Number.isInteger(Number(row.pageNumberStart)) ? Number(row.pageNumberStart) : undefined,
|
|
1229
|
+
pageNumberEnd: Number.isInteger(Number(row.pageNumberEnd)) ? Number(row.pageNumberEnd) : undefined,
|
|
1230
|
+
summaryText: this.normalizeNonEmpty(row.summaryText) ?? row.title,
|
|
1231
|
+
sourceCharCount: this.toNonNegativeInteger(row.sourceCharCount),
|
|
1232
|
+
sourceWordCount: this.toNonNegativeInteger(row.sourceWordCount),
|
|
1233
|
+
summaryCharCount: this.toNonNegativeInteger(row.summaryCharCount),
|
|
1234
|
+
summaryWordCount: this.toNonNegativeInteger(row.summaryWordCount),
|
|
1235
|
+
};
|
|
1236
|
+
}
|
|
1237
|
+
buildDocumentAnswerPack(documentVersionId, localeKey, q, limitRaw, retrievalStrategyRaw, rows) {
|
|
1238
|
+
const retrievalStrategy = this.resolveDocumentRetrievalStrategy(retrievalStrategyRaw);
|
|
1239
|
+
const normalizedQuery = this.normalizeDocumentIndexSearchText(q);
|
|
1240
|
+
const queryTokens = this.tokenizeDocumentIndexSearchTokens(normalizedQuery);
|
|
1241
|
+
const limit = this.resolveDocumentAnswerPackLimit(limitRaw);
|
|
1242
|
+
const sorted = [...rows].sort((left, right) => {
|
|
1243
|
+
const sortDelta = Number(left.sortOrder ?? 0) - Number(right.sortOrder ?? 0);
|
|
1244
|
+
if (sortDelta !== 0)
|
|
1245
|
+
return sortDelta;
|
|
1246
|
+
return String(left.id ?? '').localeCompare(String(right.id ?? ''));
|
|
1247
|
+
});
|
|
1248
|
+
const documentRow = sorted.find((row) => row.itemKind === 'document');
|
|
1249
|
+
if (sorted.length === 0 || !normalizedQuery || queryTokens.length === 0) {
|
|
1250
|
+
return Effect.succeed({
|
|
1251
|
+
documentVersionId,
|
|
1252
|
+
locale: this.normalizeNonEmpty(documentRow?.locale) ?? localeKey.locale,
|
|
1253
|
+
fallbackLocale: this.normalizeNonEmpty(documentRow?.fallbackLocale) ?? localeKey.fallbackLocale,
|
|
1254
|
+
q,
|
|
1255
|
+
built: sorted.length > 0,
|
|
1256
|
+
buildFingerprint: this.normalizeNonEmpty(documentRow?.buildFingerprint) ??
|
|
1257
|
+
this.normalizeNonEmpty(sorted[0]?.buildFingerprint),
|
|
1258
|
+
answer: '',
|
|
1259
|
+
answerSource: 'none',
|
|
1260
|
+
citations: [],
|
|
1261
|
+
provenance: {
|
|
1262
|
+
strategy: this.toDocumentAnswerPackProvenanceStrategy(retrievalStrategy),
|
|
1263
|
+
retrievalStrategy,
|
|
1264
|
+
citationCount: 0,
|
|
1265
|
+
primaryMatchedBy: [],
|
|
1266
|
+
vectorAvailable: false,
|
|
1267
|
+
},
|
|
1268
|
+
});
|
|
1269
|
+
}
|
|
1270
|
+
return Effect.gen(this, function* (_) {
|
|
1271
|
+
const vectorState = yield* _(this.resolveDocumentVectorState(sorted, normalizedQuery, retrievalStrategy, 'buildDocumentAnswerPack.vector'));
|
|
1272
|
+
const citations = this.rankDocumentAnswerPackRows(sorted, normalizedQuery, queryTokens, vectorState)
|
|
1273
|
+
.map((match) => this.toDocumentAnswerPackCitation(match.row, match, queryTokens))
|
|
1274
|
+
.sort((left, right) => {
|
|
1275
|
+
const scoreDelta = right.score - left.score;
|
|
1276
|
+
if (scoreDelta !== 0)
|
|
1277
|
+
return scoreDelta;
|
|
1278
|
+
const kindPriority = this.resolveDocumentAnswerPackKindPriority(left.itemKind) -
|
|
1279
|
+
this.resolveDocumentAnswerPackKindPriority(right.itemKind);
|
|
1280
|
+
if (kindPriority !== 0)
|
|
1281
|
+
return kindPriority;
|
|
1282
|
+
const pageDelta = (left.pageNumberStart ?? Number.MAX_SAFE_INTEGER) - (right.pageNumberStart ?? Number.MAX_SAFE_INTEGER);
|
|
1283
|
+
if (pageDelta !== 0)
|
|
1284
|
+
return pageDelta;
|
|
1285
|
+
return left.breadcrumb.localeCompare(right.breadcrumb);
|
|
1286
|
+
})
|
|
1287
|
+
.slice(0, limit);
|
|
1288
|
+
const primary = citations[0];
|
|
1289
|
+
const answerSelection = this.selectDocumentAnswerPackAnswer(primary);
|
|
1290
|
+
return {
|
|
1291
|
+
documentVersionId,
|
|
1292
|
+
locale: this.normalizeNonEmpty(documentRow?.locale) ?? localeKey.locale,
|
|
1293
|
+
fallbackLocale: this.normalizeNonEmpty(documentRow?.fallbackLocale) ?? localeKey.fallbackLocale,
|
|
1294
|
+
q,
|
|
1295
|
+
built: sorted.length > 0,
|
|
1296
|
+
buildFingerprint: this.normalizeNonEmpty(documentRow?.buildFingerprint) ??
|
|
1297
|
+
this.normalizeNonEmpty(sorted[0]?.buildFingerprint),
|
|
1298
|
+
answer: answerSelection.answer,
|
|
1299
|
+
answerSource: answerSelection.answerSource,
|
|
1300
|
+
citations,
|
|
1301
|
+
provenance: {
|
|
1302
|
+
strategy: this.toDocumentAnswerPackProvenanceStrategy(retrievalStrategy),
|
|
1303
|
+
retrievalStrategy,
|
|
1304
|
+
citationCount: citations.length,
|
|
1305
|
+
selectedAnchor: primary?.anchor,
|
|
1306
|
+
selectedItemKind: primary?.itemKind,
|
|
1307
|
+
primaryMatchedBy: primary?.matchedBy ?? [],
|
|
1308
|
+
vectorAvailable: vectorState.vectorAvailable,
|
|
1309
|
+
vectorProvider: vectorState.vectorProvider,
|
|
1310
|
+
vectorModel: vectorState.vectorModel,
|
|
1311
|
+
},
|
|
1312
|
+
};
|
|
1313
|
+
});
|
|
1314
|
+
}
|
|
1315
|
+
toDocumentAnswerPackCitation(row, match, queryTokens) {
|
|
1316
|
+
const itemKind = row.itemKind === 'document'
|
|
1317
|
+
? 'document'
|
|
1318
|
+
: row.itemKind === 'section'
|
|
1319
|
+
? 'section'
|
|
1320
|
+
: 'page';
|
|
1321
|
+
const summaryText = this.normalizeNonEmpty(row.summaryText)
|
|
1322
|
+
? this.normalizeDocumentSummarySourceText(row.summaryText)
|
|
1323
|
+
: undefined;
|
|
1324
|
+
return {
|
|
1325
|
+
itemKind,
|
|
1326
|
+
anchor: row.anchor,
|
|
1327
|
+
parentAnchor: this.normalizeNonEmpty(row.parentAnchor),
|
|
1328
|
+
number: this.normalizeNonEmpty(row.number),
|
|
1329
|
+
depth: Number(row.depth ?? 0),
|
|
1330
|
+
title: row.title,
|
|
1331
|
+
breadcrumb: row.breadcrumb,
|
|
1332
|
+
sectionId: this.normalizeNonEmpty(row.sectionId),
|
|
1333
|
+
sectionUid: this.normalizeNonEmpty(row.sectionUid),
|
|
1334
|
+
sectionSlug: this.normalizeNonEmpty(row.sectionSlug),
|
|
1335
|
+
pageId: this.normalizeNonEmpty(row.pageId),
|
|
1336
|
+
pageUid: this.normalizeNonEmpty(row.pageUid),
|
|
1337
|
+
pageVersionId: this.normalizeNonEmpty(row.pageVersionId),
|
|
1338
|
+
format: row.format,
|
|
1339
|
+
pageNumberStart: Number.isInteger(Number(row.pageNumberStart)) ? Number(row.pageNumberStart) : undefined,
|
|
1340
|
+
pageNumberEnd: Number.isInteger(Number(row.pageNumberEnd)) ? Number(row.pageNumberEnd) : undefined,
|
|
1341
|
+
score: match.score,
|
|
1342
|
+
excerpt: this.buildDocumentAnswerPackExcerpt(row, queryTokens, summaryText),
|
|
1343
|
+
matchedBy: [...match.matchedBy],
|
|
1344
|
+
lexicalScore: match.lexicalScore,
|
|
1345
|
+
...(match.semanticScore !== undefined ? { semanticScore: match.semanticScore } : {}),
|
|
1346
|
+
...(summaryText ? { summaryText } : {}),
|
|
1347
|
+
};
|
|
1348
|
+
}
|
|
1349
|
+
analyzeDocumentAnswerPackRow(row, normalizedQuery, queryTokens) {
|
|
1350
|
+
const title = this.normalizeDocumentIndexSearchText(row.title);
|
|
1351
|
+
const breadcrumb = this.normalizeDocumentIndexSearchText(row.breadcrumb);
|
|
1352
|
+
const bodyText = this.normalizeDocumentIndexSearchText(row.bodyText ?? '');
|
|
1353
|
+
const numberText = this.normalizeDocumentIndexSearchText(row.number ?? '');
|
|
1354
|
+
const summaryText = this.normalizeDocumentIndexSearchText(row.summaryText ?? '');
|
|
1355
|
+
const haystack = this.normalizeDocumentIndexSearchText([this.normalizeNonEmpty(row.searchText), summaryText].filter(Boolean).join('\n'));
|
|
1356
|
+
if (!haystack) {
|
|
1357
|
+
return { score: 0, lexicalScore: 0, matchedBy: [] };
|
|
1358
|
+
}
|
|
1359
|
+
let lexicalScore = 0;
|
|
1360
|
+
const matchedBy = [];
|
|
1361
|
+
const pushMatch = (field) => {
|
|
1362
|
+
if (!matchedBy.includes(field))
|
|
1363
|
+
matchedBy.push(field);
|
|
1364
|
+
};
|
|
1365
|
+
if (title.includes(normalizedQuery)) {
|
|
1366
|
+
lexicalScore += 48;
|
|
1367
|
+
pushMatch('title');
|
|
1368
|
+
}
|
|
1369
|
+
if (breadcrumb.includes(normalizedQuery)) {
|
|
1370
|
+
lexicalScore += 20;
|
|
1371
|
+
pushMatch('breadcrumb');
|
|
1372
|
+
}
|
|
1373
|
+
if (numberText && numberText.includes(normalizedQuery)) {
|
|
1374
|
+
lexicalScore += 18;
|
|
1375
|
+
pushMatch('number');
|
|
1376
|
+
}
|
|
1377
|
+
if (summaryText && summaryText.includes(normalizedQuery)) {
|
|
1378
|
+
lexicalScore += row.itemKind === 'document' ? 40 : 26;
|
|
1379
|
+
pushMatch('summaryText');
|
|
1380
|
+
}
|
|
1381
|
+
if (bodyText && bodyText.includes(normalizedQuery)) {
|
|
1382
|
+
lexicalScore += 10;
|
|
1383
|
+
pushMatch('bodyText');
|
|
1384
|
+
}
|
|
1385
|
+
const allTokensPresent = queryTokens.every((token) => haystack.includes(token));
|
|
1386
|
+
if (!allTokensPresent && lexicalScore === 0) {
|
|
1387
|
+
return { score: 0, lexicalScore: 0, matchedBy: [] };
|
|
1388
|
+
}
|
|
1389
|
+
if (allTokensPresent)
|
|
1390
|
+
lexicalScore += 12;
|
|
1391
|
+
for (const token of queryTokens) {
|
|
1392
|
+
if (title.includes(token)) {
|
|
1393
|
+
lexicalScore += 12;
|
|
1394
|
+
pushMatch('title');
|
|
1395
|
+
}
|
|
1396
|
+
if (breadcrumb.includes(token)) {
|
|
1397
|
+
lexicalScore += 6;
|
|
1398
|
+
pushMatch('breadcrumb');
|
|
1399
|
+
}
|
|
1400
|
+
if (numberText && numberText.includes(token)) {
|
|
1401
|
+
lexicalScore += 5;
|
|
1402
|
+
pushMatch('number');
|
|
1403
|
+
}
|
|
1404
|
+
if (summaryText && summaryText.includes(token)) {
|
|
1405
|
+
lexicalScore += 8;
|
|
1406
|
+
pushMatch('summaryText');
|
|
1407
|
+
}
|
|
1408
|
+
if (bodyText && bodyText.includes(token)) {
|
|
1409
|
+
lexicalScore += 2;
|
|
1410
|
+
pushMatch('bodyText');
|
|
1411
|
+
}
|
|
1412
|
+
}
|
|
1413
|
+
return { score: lexicalScore, lexicalScore, matchedBy };
|
|
1414
|
+
}
|
|
1415
|
+
buildDocumentAnswerPackExcerpt(row, queryTokens, summaryText) {
|
|
1416
|
+
const bodyText = this.normalizeNonEmpty(row.bodyText);
|
|
1417
|
+
if (bodyText) {
|
|
1418
|
+
const excerpt = this.extractExcerpt(bodyText, queryTokens);
|
|
1419
|
+
if (excerpt)
|
|
1420
|
+
return excerpt;
|
|
1421
|
+
}
|
|
1422
|
+
if (summaryText) {
|
|
1423
|
+
const excerpt = this.extractExcerpt(summaryText, queryTokens);
|
|
1424
|
+
if (excerpt)
|
|
1425
|
+
return excerpt;
|
|
1426
|
+
}
|
|
1427
|
+
const breadcrumbExcerpt = this.extractExcerpt(row.breadcrumb, queryTokens);
|
|
1428
|
+
if (breadcrumbExcerpt)
|
|
1429
|
+
return breadcrumbExcerpt;
|
|
1430
|
+
return row.title;
|
|
1431
|
+
}
|
|
1432
|
+
resolveDocumentAnswerPackKindPriority(itemKind) {
|
|
1433
|
+
if (itemKind === 'page')
|
|
1434
|
+
return 0;
|
|
1435
|
+
if (itemKind === 'section')
|
|
1436
|
+
return 1;
|
|
1437
|
+
return 2;
|
|
1438
|
+
}
|
|
1439
|
+
selectDocumentAnswerPackAnswer(citation) {
|
|
1440
|
+
if (!citation) {
|
|
1441
|
+
return { answer: '', answerSource: 'none' };
|
|
1442
|
+
}
|
|
1443
|
+
const summaryText = this.normalizeNonEmpty(citation.summaryText);
|
|
1444
|
+
const excerpt = this.normalizeNonEmpty(citation.excerpt);
|
|
1445
|
+
if (citation.itemKind === 'page' && excerpt && excerpt !== citation.title) {
|
|
1446
|
+
return { answer: excerpt, answerSource: 'excerpt' };
|
|
1447
|
+
}
|
|
1448
|
+
if (summaryText) {
|
|
1449
|
+
return { answer: summaryText, answerSource: 'summary' };
|
|
1450
|
+
}
|
|
1451
|
+
if (excerpt) {
|
|
1452
|
+
return { answer: excerpt, answerSource: 'excerpt' };
|
|
1453
|
+
}
|
|
1454
|
+
const title = this.normalizeNonEmpty(citation.title);
|
|
1455
|
+
if (title) {
|
|
1456
|
+
return { answer: title, answerSource: 'title' };
|
|
1457
|
+
}
|
|
1458
|
+
return { answer: '', answerSource: 'none' };
|
|
1459
|
+
}
|
|
1460
|
+
searchPersistedDocumentIndexRows(documentVersionId, localeKey, q, limitRaw, retrievalStrategyRaw, rows) {
|
|
1461
|
+
const retrievalStrategy = this.resolveDocumentRetrievalStrategy(retrievalStrategyRaw);
|
|
1462
|
+
const normalizedQuery = this.normalizeDocumentIndexSearchText(q);
|
|
1463
|
+
const queryTokens = this.tokenizeDocumentIndexSearchTokens(normalizedQuery);
|
|
1464
|
+
const limit = this.resolveDocumentIndexSearchLimit(limitRaw);
|
|
1465
|
+
const sorted = [...rows].sort((left, right) => {
|
|
1466
|
+
const sortDelta = Number(left.sortOrder ?? 0) - Number(right.sortOrder ?? 0);
|
|
1467
|
+
if (sortDelta !== 0)
|
|
1468
|
+
return sortDelta;
|
|
1469
|
+
return String(left.id ?? '').localeCompare(String(right.id ?? ''));
|
|
1470
|
+
});
|
|
1471
|
+
const documentRow = sorted.find((row) => row.itemKind === 'document');
|
|
1472
|
+
if (sorted.length === 0 || !normalizedQuery || queryTokens.length === 0) {
|
|
1473
|
+
return Effect.succeed({
|
|
1474
|
+
documentVersionId,
|
|
1475
|
+
locale: localeKey.locale,
|
|
1476
|
+
fallbackLocale: localeKey.fallbackLocale,
|
|
1477
|
+
q,
|
|
1478
|
+
built: sorted.length > 0,
|
|
1479
|
+
buildFingerprint: this.normalizeNonEmpty(documentRow?.buildFingerprint) ??
|
|
1480
|
+
this.normalizeNonEmpty(sorted[0]?.buildFingerprint),
|
|
1481
|
+
hits: [],
|
|
1482
|
+
provenance: {
|
|
1483
|
+
strategy: 'lexical-search-v1',
|
|
1484
|
+
retrievalStrategy,
|
|
1485
|
+
vectorAvailable: false,
|
|
1486
|
+
},
|
|
1487
|
+
});
|
|
1488
|
+
}
|
|
1489
|
+
return Effect.gen(this, function* (_) {
|
|
1490
|
+
const vectorState = yield* _(this.resolveDocumentVectorState(sorted, normalizedQuery, retrievalStrategy, 'searchPersistedDocumentIndexRows.vector'));
|
|
1491
|
+
const hits = this.rankPersistedDocumentSearchRows(sorted, normalizedQuery, queryTokens, vectorState)
|
|
1492
|
+
.map((match) => this.toPersistedDocumentSearchHit(match.row, match, queryTokens))
|
|
1493
|
+
.sort((left, right) => {
|
|
1494
|
+
const scoreDelta = right.score - left.score;
|
|
1495
|
+
if (scoreDelta !== 0)
|
|
1496
|
+
return scoreDelta;
|
|
1497
|
+
const pageDelta = (left.pageNumberStart ?? Number.MAX_SAFE_INTEGER) - (right.pageNumberStart ?? Number.MAX_SAFE_INTEGER);
|
|
1498
|
+
if (pageDelta !== 0)
|
|
1499
|
+
return pageDelta;
|
|
1500
|
+
return left.breadcrumb.localeCompare(right.breadcrumb);
|
|
1501
|
+
})
|
|
1502
|
+
.slice(0, limit);
|
|
1503
|
+
return {
|
|
1504
|
+
documentVersionId,
|
|
1505
|
+
locale: this.normalizeNonEmpty(documentRow?.locale) ?? localeKey.locale,
|
|
1506
|
+
fallbackLocale: this.normalizeNonEmpty(documentRow?.fallbackLocale) ?? localeKey.fallbackLocale,
|
|
1507
|
+
q,
|
|
1508
|
+
built: sorted.length > 0,
|
|
1509
|
+
buildFingerprint: this.normalizeNonEmpty(documentRow?.buildFingerprint) ??
|
|
1510
|
+
this.normalizeNonEmpty(sorted[0]?.buildFingerprint),
|
|
1511
|
+
hits,
|
|
1512
|
+
provenance: this.toDocumentSearchProvenance(retrievalStrategy, vectorState),
|
|
1513
|
+
};
|
|
1514
|
+
});
|
|
1515
|
+
}
|
|
1516
|
+
toPersistedDocumentSearchHit(row, match, queryTokens) {
|
|
1517
|
+
return {
|
|
1518
|
+
itemKind: row.itemKind === 'section' ? 'section' : 'page',
|
|
1519
|
+
anchor: row.anchor,
|
|
1520
|
+
parentAnchor: this.normalizeNonEmpty(row.parentAnchor),
|
|
1521
|
+
number: this.normalizeNonEmpty(row.number),
|
|
1522
|
+
depth: Number(row.depth ?? 0),
|
|
1523
|
+
title: row.title,
|
|
1524
|
+
breadcrumb: row.breadcrumb,
|
|
1525
|
+
sectionId: this.normalizeNonEmpty(row.sectionId),
|
|
1526
|
+
sectionUid: this.normalizeNonEmpty(row.sectionUid),
|
|
1527
|
+
sectionSlug: this.normalizeNonEmpty(row.sectionSlug),
|
|
1528
|
+
pageId: this.normalizeNonEmpty(row.pageId),
|
|
1529
|
+
pageUid: this.normalizeNonEmpty(row.pageUid),
|
|
1530
|
+
pageVersionId: this.normalizeNonEmpty(row.pageVersionId),
|
|
1531
|
+
format: row.format,
|
|
1532
|
+
pageNumberStart: Number.isInteger(Number(row.pageNumberStart)) ? Number(row.pageNumberStart) : undefined,
|
|
1533
|
+
pageNumberEnd: Number.isInteger(Number(row.pageNumberEnd)) ? Number(row.pageNumberEnd) : undefined,
|
|
1534
|
+
score: match.score,
|
|
1535
|
+
excerpt: this.buildPersistedDocumentIndexExcerpt(row, queryTokens),
|
|
1536
|
+
matchedBy: [...match.matchedBy],
|
|
1537
|
+
lexicalScore: match.lexicalScore,
|
|
1538
|
+
...(match.semanticScore !== undefined ? { semanticScore: match.semanticScore } : {}),
|
|
1539
|
+
};
|
|
1540
|
+
}
|
|
1541
|
+
toDocumentIndexBoolean(value) {
|
|
1542
|
+
return value === true || value === 1 || value === '1';
|
|
1543
|
+
}
|
|
1544
|
+
toNonNegativeInteger(value) {
|
|
1545
|
+
const parsed = Number(value);
|
|
1546
|
+
if (!Number.isInteger(parsed) || parsed < 0)
|
|
1547
|
+
return 0;
|
|
1548
|
+
return parsed;
|
|
1549
|
+
}
|
|
1550
|
+
scorePersistedDocumentIndexRow(row, normalizedQuery, queryTokens) {
|
|
1551
|
+
const haystack = this.normalizeNonEmpty(row.searchText);
|
|
1552
|
+
if (!haystack) {
|
|
1553
|
+
return { row, score: 0, lexicalScore: 0, matchedBy: [] };
|
|
1554
|
+
}
|
|
1555
|
+
let lexicalScore = 0;
|
|
1556
|
+
const title = this.normalizeDocumentIndexSearchText(row.title);
|
|
1557
|
+
const breadcrumb = this.normalizeDocumentIndexSearchText(row.breadcrumb);
|
|
1558
|
+
const bodyText = this.normalizeDocumentIndexSearchText(row.bodyText ?? '');
|
|
1559
|
+
const numberText = this.normalizeDocumentIndexSearchText(row.number ?? '');
|
|
1560
|
+
const matchedBy = [];
|
|
1561
|
+
const pushMatch = (field) => {
|
|
1562
|
+
if (!matchedBy.includes(field))
|
|
1563
|
+
matchedBy.push(field);
|
|
1564
|
+
};
|
|
1565
|
+
if (haystack.includes(normalizedQuery))
|
|
1566
|
+
lexicalScore += 24;
|
|
1567
|
+
if (title.includes(normalizedQuery)) {
|
|
1568
|
+
lexicalScore += 48;
|
|
1569
|
+
pushMatch('title');
|
|
1570
|
+
}
|
|
1571
|
+
if (breadcrumb.includes(normalizedQuery)) {
|
|
1572
|
+
lexicalScore += 20;
|
|
1573
|
+
pushMatch('breadcrumb');
|
|
1574
|
+
}
|
|
1575
|
+
if (numberText && numberText.includes(normalizedQuery)) {
|
|
1576
|
+
lexicalScore += 18;
|
|
1577
|
+
pushMatch('number');
|
|
1578
|
+
}
|
|
1579
|
+
if (bodyText && bodyText.includes(normalizedQuery)) {
|
|
1580
|
+
lexicalScore += 8;
|
|
1581
|
+
pushMatch('bodyText');
|
|
1582
|
+
}
|
|
1583
|
+
const allTokensPresent = queryTokens.every((token) => haystack.includes(token));
|
|
1584
|
+
if (!allTokensPresent && lexicalScore === 0) {
|
|
1585
|
+
return { row, score: 0, lexicalScore: 0, matchedBy: [] };
|
|
1586
|
+
}
|
|
1587
|
+
if (allTokensPresent)
|
|
1588
|
+
lexicalScore += 12;
|
|
1589
|
+
for (const token of queryTokens) {
|
|
1590
|
+
if (title.includes(token)) {
|
|
1591
|
+
lexicalScore += 12;
|
|
1592
|
+
pushMatch('title');
|
|
1593
|
+
}
|
|
1594
|
+
if (breadcrumb.includes(token)) {
|
|
1595
|
+
lexicalScore += 6;
|
|
1596
|
+
pushMatch('breadcrumb');
|
|
1597
|
+
}
|
|
1598
|
+
if (numberText && numberText.includes(token)) {
|
|
1599
|
+
lexicalScore += 5;
|
|
1600
|
+
pushMatch('number');
|
|
1601
|
+
}
|
|
1602
|
+
if (bodyText && bodyText.includes(token)) {
|
|
1603
|
+
lexicalScore += 2;
|
|
1604
|
+
pushMatch('bodyText');
|
|
1605
|
+
}
|
|
1606
|
+
}
|
|
1607
|
+
return { row, score: lexicalScore, lexicalScore, matchedBy };
|
|
1608
|
+
}
|
|
1609
|
+
resolveDocumentRetrievalStrategy(value) {
|
|
1610
|
+
return value === 'hybrid' || value === 'semantic' ? value : 'lexical';
|
|
1611
|
+
}
|
|
1612
|
+
toDocumentAnswerPackProvenanceStrategy(retrievalStrategy) {
|
|
1613
|
+
return retrievalStrategy === 'semantic'
|
|
1614
|
+
? 'semantic-answer-pack-v1'
|
|
1615
|
+
: retrievalStrategy === 'hybrid'
|
|
1616
|
+
? 'hybrid-answer-pack-v1'
|
|
1617
|
+
: 'deterministic-answer-pack-v1';
|
|
1618
|
+
}
|
|
1619
|
+
toDocumentSearchProvenance(retrievalStrategy, vectorState) {
|
|
1620
|
+
return {
|
|
1621
|
+
strategy: this.toDocumentSearchStrategy(retrievalStrategy),
|
|
1622
|
+
retrievalStrategy,
|
|
1623
|
+
vectorAvailable: vectorState.vectorAvailable,
|
|
1624
|
+
vectorProvider: vectorState.vectorProvider,
|
|
1625
|
+
vectorModel: vectorState.vectorModel,
|
|
1626
|
+
};
|
|
1627
|
+
}
|
|
1628
|
+
toDocumentSearchStrategy(retrievalStrategy) {
|
|
1629
|
+
return retrievalStrategy === 'semantic'
|
|
1630
|
+
? 'semantic-search-v1'
|
|
1631
|
+
: retrievalStrategy === 'hybrid'
|
|
1632
|
+
? 'hybrid-search-v1'
|
|
1633
|
+
: 'lexical-search-v1';
|
|
1634
|
+
}
|
|
1635
|
+
resolveScopeSearchDocumentSeed(document) {
|
|
1636
|
+
const versions = Array.isArray(document.documentVersions) ? [...document.documentVersions] : [];
|
|
1637
|
+
// Prefer the row marked isCurrent=true (set via docman.document-version.set-current).
|
|
1638
|
+
// Fall back to highest numeric version when no current row exists — keeps backward
|
|
1639
|
+
// compatibility for documents that pre-date the set-current invariant.
|
|
1640
|
+
const currentVersion = versions.find((version) => version.isCurrent === true && this.normalizeNonEmpty(version.id));
|
|
1641
|
+
const sortedVersions = versions
|
|
1642
|
+
.slice()
|
|
1643
|
+
.sort((left, right) => Number(right.version ?? 0) - Number(left.version ?? 0));
|
|
1644
|
+
const latestVersion = currentVersion ?? sortedVersions.find((version) => this.normalizeNonEmpty(version.id));
|
|
1645
|
+
const documentId = this.normalizeNonEmpty(document.id);
|
|
1646
|
+
const documentTitle = this.normalizeNonEmpty(document.title);
|
|
1647
|
+
const documentVersionId = this.normalizeNonEmpty(latestVersion?.id);
|
|
1648
|
+
if (!documentId || !documentTitle || !documentVersionId)
|
|
1649
|
+
return null;
|
|
1650
|
+
return {
|
|
1651
|
+
documentId,
|
|
1652
|
+
documentTitle,
|
|
1653
|
+
documentSlug: this.normalizeNonEmpty(document.slug),
|
|
1654
|
+
documentVersionId,
|
|
1655
|
+
documentVersionTitle: this.normalizeNonEmpty(latestVersion?.title) ?? `v${Number(latestVersion?.version ?? 0) || '?'}`,
|
|
1656
|
+
documentVersionNumber: Number.isFinite(Number(latestVersion?.version))
|
|
1657
|
+
? Number(latestVersion?.version)
|
|
1658
|
+
: undefined,
|
|
1659
|
+
};
|
|
1660
|
+
}
|
|
1661
|
+
toScopeDocumentSearchHit(seed, hit) {
|
|
1662
|
+
return {
|
|
1663
|
+
...hit,
|
|
1664
|
+
documentId: seed.documentId,
|
|
1665
|
+
documentTitle: seed.documentTitle,
|
|
1666
|
+
documentSlug: seed.documentSlug,
|
|
1667
|
+
documentVersionId: seed.documentVersionId,
|
|
1668
|
+
documentVersionTitle: seed.documentVersionTitle,
|
|
1669
|
+
documentVersionNumber: seed.documentVersionNumber,
|
|
1670
|
+
};
|
|
1671
|
+
}
|
|
1672
|
+
describeScopeSearchError(error) {
|
|
1673
|
+
const info = effectErrorInfo(error);
|
|
1674
|
+
if (error instanceof Error && this.normalizeNonEmpty(error.message)) {
|
|
1675
|
+
return error.message;
|
|
1676
|
+
}
|
|
1677
|
+
if (typeof info.unwrapped === 'string' && this.normalizeNonEmpty(info.unwrapped)) {
|
|
1678
|
+
return info.unwrapped;
|
|
1679
|
+
}
|
|
1680
|
+
if (info.unwrapped instanceof Error && this.normalizeNonEmpty(info.unwrapped.message)) {
|
|
1681
|
+
return info.unwrapped.message;
|
|
1682
|
+
}
|
|
1683
|
+
try {
|
|
1684
|
+
return JSON.stringify(info.unwrapped) ?? 'Unknown scope search error.';
|
|
1685
|
+
}
|
|
1686
|
+
catch {
|
|
1687
|
+
return 'Unknown scope search error.';
|
|
1688
|
+
}
|
|
1689
|
+
}
|
|
1690
|
+
rankPersistedDocumentSearchRows(rows, normalizedQuery, queryTokens, vectorState) {
|
|
1691
|
+
return rows
|
|
1692
|
+
.filter((row) => row.itemKind === 'section' || row.itemKind === 'page')
|
|
1693
|
+
.map((row) => {
|
|
1694
|
+
const lexical = this.scorePersistedDocumentIndexRow(row, normalizedQuery, queryTokens);
|
|
1695
|
+
return this.applySemanticScoreToMatch(lexical, row, vectorState);
|
|
1696
|
+
})
|
|
1697
|
+
.filter((match) => match.score > 0);
|
|
1698
|
+
}
|
|
1699
|
+
rankDocumentAnswerPackRows(rows, normalizedQuery, queryTokens, vectorState) {
|
|
1700
|
+
return rows
|
|
1701
|
+
.map((row) => {
|
|
1702
|
+
const lexical = this.analyzeDocumentAnswerPackRow(row, normalizedQuery, queryTokens);
|
|
1703
|
+
return this.applySemanticScoreToMatch({ row, ...lexical }, row, vectorState);
|
|
1704
|
+
})
|
|
1705
|
+
.filter((match) => match.score > 0);
|
|
1706
|
+
}
|
|
1707
|
+
applySemanticScoreToMatch(baseMatch, row, vectorState) {
|
|
1708
|
+
const semanticScore = this.resolvePersistedDocumentSemanticScore(row, vectorState);
|
|
1709
|
+
const matchedBy = [...baseMatch.matchedBy];
|
|
1710
|
+
if (semanticScore > 0.12 && !matchedBy.includes('semanticVector')) {
|
|
1711
|
+
matchedBy.push('semanticVector');
|
|
1712
|
+
}
|
|
1713
|
+
const score = vectorState.retrievalStrategy === 'semantic'
|
|
1714
|
+
? semanticScore > 0.12
|
|
1715
|
+
? semanticScore * 100 + baseMatch.lexicalScore * 0.2
|
|
1716
|
+
: 0
|
|
1717
|
+
: vectorState.retrievalStrategy === 'hybrid'
|
|
1718
|
+
? baseMatch.lexicalScore + semanticScore * 48
|
|
1719
|
+
: baseMatch.lexicalScore;
|
|
1720
|
+
return {
|
|
1721
|
+
...baseMatch,
|
|
1722
|
+
matchedBy,
|
|
1723
|
+
semanticScore: semanticScore > 0 ? Number(semanticScore.toFixed(6)) : undefined,
|
|
1724
|
+
score: Number(score.toFixed(6)),
|
|
1725
|
+
};
|
|
1726
|
+
}
|
|
1727
|
+
resolvePersistedDocumentSemanticScore(row, vectorState) {
|
|
1728
|
+
if (!vectorState.vectorAvailable || !vectorState.queryVector?.length)
|
|
1729
|
+
return 0;
|
|
1730
|
+
if (this.normalizeNonEmpty(row.embeddingProvider) !== this.normalizeNonEmpty(vectorState.vectorProvider))
|
|
1731
|
+
return 0;
|
|
1732
|
+
if (this.normalizeNonEmpty(row.embeddingModel) !== this.normalizeNonEmpty(vectorState.vectorModel))
|
|
1733
|
+
return 0;
|
|
1734
|
+
const storedVector = parseDocmanEmbeddingVector(row.embeddingVector);
|
|
1735
|
+
if (!storedVector?.length)
|
|
1736
|
+
return 0;
|
|
1737
|
+
return Math.max(0, cosineSimilarity(vectorState.queryVector, storedVector));
|
|
1738
|
+
}
|
|
1739
|
+
resolveDocumentVectorState(rows, normalizedQuery, retrievalStrategy, stage) {
|
|
1740
|
+
if (retrievalStrategy === 'lexical') {
|
|
1741
|
+
return Effect.succeed({
|
|
1742
|
+
retrievalStrategy,
|
|
1743
|
+
vectorAvailable: false,
|
|
1744
|
+
});
|
|
1745
|
+
}
|
|
1746
|
+
const embeddedRow = rows.find((row) => Boolean(parseDocmanEmbeddingVector(row.embeddingVector)?.length) &&
|
|
1747
|
+
Boolean(this.normalizeNonEmpty(row.embeddingProvider)) &&
|
|
1748
|
+
Boolean(this.normalizeNonEmpty(row.embeddingModel)));
|
|
1749
|
+
if (!embeddedRow) {
|
|
1750
|
+
return Effect.succeed({
|
|
1751
|
+
retrievalStrategy,
|
|
1752
|
+
vectorAvailable: false,
|
|
1753
|
+
});
|
|
1754
|
+
}
|
|
1755
|
+
const embeddedProvider = this.normalizeNonEmpty(embeddedRow.embeddingProvider);
|
|
1756
|
+
const embeddedModel = this.normalizeNonEmpty(embeddedRow.embeddingModel);
|
|
1757
|
+
const provider = embeddedProvider === this.embeddingProvider.provider && embeddedModel === this.embeddingProvider.model
|
|
1758
|
+
? this.embeddingProvider
|
|
1759
|
+
: (() => {
|
|
1760
|
+
const localProvider = createDocmanLocalHashEmbeddingProvider();
|
|
1761
|
+
return embeddedProvider === localProvider.provider && embeddedModel === localProvider.model
|
|
1762
|
+
? localProvider
|
|
1763
|
+
: undefined;
|
|
1764
|
+
})();
|
|
1765
|
+
if (!provider) {
|
|
1766
|
+
return Effect.succeed({
|
|
1767
|
+
retrievalStrategy,
|
|
1768
|
+
vectorAvailable: false,
|
|
1769
|
+
});
|
|
1770
|
+
}
|
|
1771
|
+
return Effect.tryPromise({
|
|
1772
|
+
try: async () => {
|
|
1773
|
+
const result = await provider.embedMany({ texts: [normalizedQuery] });
|
|
1774
|
+
const queryVector = result.vectors[0];
|
|
1775
|
+
return {
|
|
1776
|
+
retrievalStrategy,
|
|
1777
|
+
vectorAvailable: Array.isArray(queryVector) && queryVector.length > 0,
|
|
1778
|
+
vectorProvider: result.provider,
|
|
1779
|
+
vectorModel: result.model,
|
|
1780
|
+
queryVector,
|
|
1781
|
+
};
|
|
1782
|
+
},
|
|
1783
|
+
catch: (error) => XfErrorFactory.upsertFailed({
|
|
1784
|
+
stage,
|
|
1785
|
+
operation: 'embeddingProvider.embedMany',
|
|
1786
|
+
message: 'Failed to build document retrieval query vector.',
|
|
1787
|
+
cause: error,
|
|
1788
|
+
}),
|
|
1789
|
+
});
|
|
1790
|
+
}
|
|
1791
|
+
buildPersistedDocumentIndexExcerpt(row, queryTokens) {
|
|
1792
|
+
const bodyText = this.normalizeNonEmpty(row.bodyText);
|
|
1793
|
+
if (bodyText) {
|
|
1794
|
+
const excerpt = this.extractExcerpt(bodyText, queryTokens);
|
|
1795
|
+
if (excerpt)
|
|
1796
|
+
return excerpt;
|
|
1797
|
+
}
|
|
1798
|
+
const breadcrumbExcerpt = this.extractExcerpt(row.breadcrumb, queryTokens);
|
|
1799
|
+
if (breadcrumbExcerpt)
|
|
1800
|
+
return breadcrumbExcerpt;
|
|
1801
|
+
return row.title;
|
|
1802
|
+
}
|
|
1803
|
+
extractExcerpt(source, queryTokens) {
|
|
1804
|
+
const normalizedSource = String(source ?? '').replace(/\s+/g, ' ').trim();
|
|
1805
|
+
if (!normalizedSource)
|
|
1806
|
+
return '';
|
|
1807
|
+
const loweredSource = normalizedSource.toLowerCase();
|
|
1808
|
+
let matchIndex = -1;
|
|
1809
|
+
let matchedToken = '';
|
|
1810
|
+
for (const token of queryTokens) {
|
|
1811
|
+
const candidate = loweredSource.indexOf(token.toLowerCase());
|
|
1812
|
+
if (candidate === -1)
|
|
1813
|
+
continue;
|
|
1814
|
+
if (matchIndex === -1 || candidate < matchIndex) {
|
|
1815
|
+
matchIndex = candidate;
|
|
1816
|
+
matchedToken = token;
|
|
1817
|
+
}
|
|
1818
|
+
}
|
|
1819
|
+
if (matchIndex === -1) {
|
|
1820
|
+
return normalizedSource.slice(0, 160);
|
|
1821
|
+
}
|
|
1822
|
+
const start = Math.max(0, matchIndex - 48);
|
|
1823
|
+
const end = Math.min(normalizedSource.length, matchIndex + Math.max(matchedToken.length, 24) + 80);
|
|
1824
|
+
const prefix = start > 0 ? '...' : '';
|
|
1825
|
+
const suffix = end < normalizedSource.length ? '...' : '';
|
|
1826
|
+
return `${prefix}${normalizedSource.slice(start, end).trim()}${suffix}`;
|
|
1827
|
+
}
|
|
1828
|
+
resolveDocumentIndexSearchLimit(value) {
|
|
1829
|
+
const parsed = Number(value);
|
|
1830
|
+
if (!Number.isInteger(parsed) || parsed <= 0)
|
|
1831
|
+
return 8;
|
|
1832
|
+
return Math.max(1, Math.min(20, parsed));
|
|
1833
|
+
}
|
|
1834
|
+
resolveDocumentAnswerPackLimit(value) {
|
|
1835
|
+
const parsed = Number(value);
|
|
1836
|
+
if (!Number.isInteger(parsed) || parsed <= 0)
|
|
1837
|
+
return 3;
|
|
1838
|
+
return Math.max(1, Math.min(8, parsed));
|
|
1839
|
+
}
|
|
1840
|
+
tokenizeDocumentIndexSearchTokens(value) {
|
|
1841
|
+
return [...new Set(value.split(/\s+/).map((token) => token.trim()).filter(Boolean))];
|
|
1842
|
+
}
|
|
1843
|
+
normalizeDocumentIndexSearchText(value) {
|
|
1844
|
+
return String(value ?? '')
|
|
1845
|
+
.toLowerCase()
|
|
1846
|
+
.replace(/<!--[\s\S]*?-->/g, ' ')
|
|
1847
|
+
.replace(/<\/?[^>]+>/g, ' ')
|
|
1848
|
+
.replace(/[`*_~>#=+|]/g, ' ')
|
|
1849
|
+
.replace(/\[[^\]]*]\(([^)]+)\)/g, ' ')
|
|
1850
|
+
.replace(/[^\p{L}\p{N}.\- ]+/gu, ' ')
|
|
1851
|
+
.replace(/\s+/g, ' ')
|
|
1852
|
+
.trim();
|
|
1853
|
+
}
|
|
1854
|
+
buildPersistedDocumentIndexEmbeddingText(row) {
|
|
1855
|
+
return this.normalizeDocumentIndexSearchText([
|
|
1856
|
+
this.normalizeNonEmpty(row.number),
|
|
1857
|
+
this.normalizeNonEmpty(row.title),
|
|
1858
|
+
this.normalizeNonEmpty(row.breadcrumb),
|
|
1859
|
+
this.normalizeNonEmpty(row.summaryText),
|
|
1860
|
+
this.normalizeNonEmpty(row.bodyText),
|
|
1861
|
+
]
|
|
1862
|
+
.filter(Boolean)
|
|
1863
|
+
.join('\n'));
|
|
1864
|
+
}
|
|
1865
|
+
populatePersistedDocumentIndexEmbeddings(rows, stage, operation) {
|
|
1866
|
+
if (rows.length === 0)
|
|
1867
|
+
return Effect.succeed([]);
|
|
1868
|
+
const embeddingTexts = rows.map((row) => this.buildPersistedDocumentIndexEmbeddingText(row));
|
|
1869
|
+
return Effect.tryPromise({
|
|
1870
|
+
try: async () => {
|
|
1871
|
+
let result;
|
|
1872
|
+
try {
|
|
1873
|
+
result = await this.embeddingProvider.embedMany({ texts: embeddingTexts });
|
|
1874
|
+
}
|
|
1875
|
+
catch (error) {
|
|
1876
|
+
if (this.embeddingProvider.provider === createDocmanLocalHashEmbeddingProvider().provider) {
|
|
1877
|
+
throw error;
|
|
1878
|
+
}
|
|
1879
|
+
this.logger?.warn({
|
|
1880
|
+
stage,
|
|
1881
|
+
operation,
|
|
1882
|
+
provider: this.embeddingProvider.provider,
|
|
1883
|
+
model: this.embeddingProvider.model,
|
|
1884
|
+
}, 'Embedding provider failed; falling back to local hash embeddings.');
|
|
1885
|
+
result = await createDocmanLocalHashEmbeddingProvider().embedMany({ texts: embeddingTexts });
|
|
1886
|
+
}
|
|
1887
|
+
return rows.map((row, index) => {
|
|
1888
|
+
const embeddingText = embeddingTexts[index];
|
|
1889
|
+
const vector = result.vectors[index];
|
|
1890
|
+
if (!embeddingText || !Array.isArray(vector) || vector.length === 0) {
|
|
1891
|
+
return {
|
|
1892
|
+
...row,
|
|
1893
|
+
embeddingProvider: undefined,
|
|
1894
|
+
embeddingModel: undefined,
|
|
1895
|
+
embeddingHash: undefined,
|
|
1896
|
+
embeddingDimensions: undefined,
|
|
1897
|
+
embeddingVector: undefined,
|
|
1898
|
+
};
|
|
1899
|
+
}
|
|
1900
|
+
return {
|
|
1901
|
+
...row,
|
|
1902
|
+
embeddingProvider: result.provider,
|
|
1903
|
+
embeddingModel: result.model,
|
|
1904
|
+
embeddingHash: buildDocmanEmbeddingHash(embeddingText),
|
|
1905
|
+
embeddingDimensions: result.dimensions || vector.length,
|
|
1906
|
+
embeddingVector: serializeDocmanEmbeddingVector(vector),
|
|
1907
|
+
};
|
|
1908
|
+
});
|
|
1909
|
+
},
|
|
1910
|
+
catch: (error) => XfErrorFactory.upsertFailed({
|
|
1911
|
+
stage,
|
|
1912
|
+
operation,
|
|
1913
|
+
message: 'Failed to populate document retrieval embeddings.',
|
|
1914
|
+
cause: error,
|
|
1915
|
+
}),
|
|
1916
|
+
});
|
|
1917
|
+
}
|
|
1918
|
+
refreshPersistedDocumentIndexEmbeddings(repository, rows, stage, operation) {
|
|
1919
|
+
if (rows.length === 0)
|
|
1920
|
+
return Effect.succeed(rows);
|
|
1921
|
+
return Effect.gen(this, function* (_) {
|
|
1922
|
+
const embeddedRows = yield* _(this.populatePersistedDocumentIndexEmbeddings(rows, stage, operation));
|
|
1923
|
+
const patches = [];
|
|
1924
|
+
for (const row of embeddedRows) {
|
|
1925
|
+
const id = this.normalizeNonEmpty(row.id);
|
|
1926
|
+
if (!id)
|
|
1927
|
+
continue;
|
|
1928
|
+
patches.push({
|
|
1929
|
+
id,
|
|
1930
|
+
patch: {
|
|
1931
|
+
embeddingProvider: row.embeddingProvider,
|
|
1932
|
+
embeddingModel: row.embeddingModel,
|
|
1933
|
+
embeddingHash: row.embeddingHash,
|
|
1934
|
+
embeddingDimensions: row.embeddingDimensions,
|
|
1935
|
+
embeddingVector: row.embeddingVector,
|
|
1936
|
+
},
|
|
1937
|
+
});
|
|
1938
|
+
}
|
|
1939
|
+
yield* _(Effect.all(patches.map(({ id, patch }) => repository.patchById(id, patch).pipe(Effect.mapError(mapDbError({
|
|
1940
|
+
stage,
|
|
1941
|
+
operation: 'documentIndexEntryRepository.patchById',
|
|
1942
|
+
factory: XfErrorFactory.upsertFailed,
|
|
1943
|
+
})))), { concurrency: 6 }));
|
|
1944
|
+
return embeddedRows;
|
|
1945
|
+
});
|
|
1946
|
+
}
|
|
1947
|
+
extractDocumentIndexBodyText(item) {
|
|
1948
|
+
const source = item.contentParts.join('\n\n');
|
|
1949
|
+
if (!source.trim())
|
|
1950
|
+
return '';
|
|
1951
|
+
return String(source)
|
|
1952
|
+
.replace(/<!--[\s\S]*?-->/g, ' ')
|
|
1953
|
+
.replace(/```[\s\S]*?```/g, (block) => block.replace(/```[^\n]*\n?/g, '').replace(/```/g, ' '))
|
|
1954
|
+
.replace(/`([^`]+)`/g, '$1')
|
|
1955
|
+
.replace(/!\[([^\]]*)]\(([^)]+)\)/g, '$1')
|
|
1956
|
+
.replace(/\[([^\]]+)]\(([^)]+)\)/g, '$1')
|
|
1957
|
+
.replace(/<\/?[^>]+>/g, ' ')
|
|
1958
|
+
.replace(/^[ \t]*[-*+]\s+/gm, '')
|
|
1959
|
+
.replace(/^[ \t]*\d+\.\s+/gm, '')
|
|
1960
|
+
.replace(/^[ \t]*#{1,6}\s+/gm, '')
|
|
1961
|
+
.replace(/\s+/g, ' ')
|
|
1962
|
+
.trim();
|
|
1963
|
+
}
|
|
1964
|
+
resolvePersistedDocumentAuthoredSummaryText(rows, localeKey, stage, operation) {
|
|
1965
|
+
const documentRow = rows.find((row) => row.itemKind === 'document');
|
|
1966
|
+
const documentId = this.normalizeNonEmpty(documentRow?.documentId);
|
|
1967
|
+
const documentVersionId = this.normalizeNonEmpty(documentRow?.documentVersionId);
|
|
1968
|
+
if (!documentId || !documentVersionId)
|
|
1969
|
+
return Effect.succeed(undefined);
|
|
1970
|
+
const localeState = {
|
|
1971
|
+
locale: localeKey.locale,
|
|
1972
|
+
fallbackLocale: localeKey.fallbackLocale,
|
|
1973
|
+
};
|
|
1974
|
+
const documentQueryOptions = this.withLocaleOptions(undefined, localeState, ['titleMl', 'summaryMl', 'descriptionMl']);
|
|
1975
|
+
return Effect.gen(this, function* (_) {
|
|
1976
|
+
const document = yield* _(this.documentRepository.findById(documentId, documentQueryOptions).pipe(Effect.mapError(mapDbError({
|
|
1977
|
+
stage,
|
|
1978
|
+
operation: `${operation}.documentRepository.findById`,
|
|
1979
|
+
factory: XfErrorFactory.notFound,
|
|
1980
|
+
}))));
|
|
1981
|
+
const version = this.documentVersionRepository
|
|
1982
|
+
? yield* _(this.documentVersionRepository.findById(documentVersionId).pipe(Effect.mapError(mapDbError({
|
|
1983
|
+
stage,
|
|
1984
|
+
operation: `${operation}.documentVersionRepository.findById`,
|
|
1985
|
+
factory: XfErrorFactory.notFound,
|
|
1986
|
+
}))))
|
|
1987
|
+
: null;
|
|
1988
|
+
const versionSummary = this.normalizeNonEmpty(version?.summary);
|
|
1989
|
+
if (versionSummary)
|
|
1990
|
+
return versionSummary;
|
|
1991
|
+
const localizedDocumentSummary = document
|
|
1992
|
+
? this.normalizeNonEmpty(this.resolveLocalizedValue(document.summaryMl, localeState))
|
|
1993
|
+
: undefined;
|
|
1994
|
+
if (localizedDocumentSummary)
|
|
1995
|
+
return localizedDocumentSummary;
|
|
1996
|
+
return this.normalizeNonEmpty(document?.summary);
|
|
1997
|
+
});
|
|
1998
|
+
}
|
|
1999
|
+
toPersistedDocumentSummaryPatches(rows, authoredDocumentSummary) {
|
|
2000
|
+
const metricsByAnchor = this.buildPersistedDocumentSummaryMetrics(rows, authoredDocumentSummary);
|
|
2001
|
+
const patches = [];
|
|
2002
|
+
for (const row of rows) {
|
|
2003
|
+
const id = this.normalizeNonEmpty(row.id);
|
|
2004
|
+
if (!id)
|
|
2005
|
+
continue;
|
|
2006
|
+
const metrics = metricsByAnchor.get(row.anchor);
|
|
2007
|
+
if (!metrics)
|
|
2008
|
+
continue;
|
|
2009
|
+
patches.push({
|
|
2010
|
+
id,
|
|
2011
|
+
patch: {
|
|
2012
|
+
summaryText: metrics.summaryText,
|
|
2013
|
+
sourceCharCount: metrics.sourceCharCount,
|
|
2014
|
+
sourceWordCount: metrics.sourceWordCount,
|
|
2015
|
+
summaryCharCount: metrics.summaryCharCount,
|
|
2016
|
+
summaryWordCount: metrics.summaryWordCount,
|
|
2017
|
+
updatedBy: DOCMAN_DOCUMENT_SUMMARY_BUILD_ACTOR,
|
|
2018
|
+
},
|
|
2019
|
+
});
|
|
2020
|
+
}
|
|
2021
|
+
return patches;
|
|
2022
|
+
}
|
|
2023
|
+
buildPersistedDocumentSummaryMetrics(rows, authoredDocumentSummary) {
|
|
2024
|
+
const sorted = [...rows].sort((left, right) => {
|
|
2025
|
+
const sortDelta = Number(left.sortOrder ?? 0) - Number(right.sortOrder ?? 0);
|
|
2026
|
+
if (sortDelta !== 0)
|
|
2027
|
+
return sortDelta;
|
|
2028
|
+
return String(left.id ?? '').localeCompare(String(right.id ?? ''));
|
|
2029
|
+
});
|
|
2030
|
+
const childrenByParentAnchor = new Map();
|
|
2031
|
+
for (const row of sorted) {
|
|
2032
|
+
const parentAnchor = this.normalizeNonEmpty(row.parentAnchor);
|
|
2033
|
+
if (!parentAnchor)
|
|
2034
|
+
continue;
|
|
2035
|
+
const siblings = childrenByParentAnchor.get(parentAnchor) ?? [];
|
|
2036
|
+
siblings.push(row);
|
|
2037
|
+
childrenByParentAnchor.set(parentAnchor, siblings);
|
|
2038
|
+
}
|
|
2039
|
+
const metricsByAnchor = new Map();
|
|
2040
|
+
const visit = (row) => {
|
|
2041
|
+
const existing = metricsByAnchor.get(row.anchor);
|
|
2042
|
+
if (existing)
|
|
2043
|
+
return existing;
|
|
2044
|
+
const childMetrics = (childrenByParentAnchor.get(row.anchor) ?? []).map((child) => visit(child));
|
|
2045
|
+
const ownBodyText = row.itemKind === 'page' ? this.normalizeDocumentSummarySourceText(row.bodyText) : '';
|
|
2046
|
+
const sourceText = this.normalizeDocumentSummarySourceText([
|
|
2047
|
+
row.title,
|
|
2048
|
+
ownBodyText,
|
|
2049
|
+
childMetrics.map((entry) => entry.sourceText).filter(Boolean).join('\n\n'),
|
|
2050
|
+
]
|
|
2051
|
+
.filter(Boolean)
|
|
2052
|
+
.join('\n\n')) || row.title;
|
|
2053
|
+
const authoredSummary = row.itemKind === 'document' ? this.normalizeDocumentSummarySourceText(authoredDocumentSummary) : '';
|
|
2054
|
+
const summaryText = authoredSummary
|
|
2055
|
+
? this.truncateDocumentSummaryText(authoredSummary, 220)
|
|
2056
|
+
: this.buildDeterministicDocumentSummaryText(sourceText, row.title);
|
|
2057
|
+
const metrics = {
|
|
2058
|
+
sourceText,
|
|
2059
|
+
summaryText,
|
|
2060
|
+
sourceCharCount: sourceText.length,
|
|
2061
|
+
sourceWordCount: this.countDocumentSummaryWords(sourceText),
|
|
2062
|
+
summaryCharCount: summaryText.length,
|
|
2063
|
+
summaryWordCount: this.countDocumentSummaryWords(summaryText),
|
|
2064
|
+
};
|
|
2065
|
+
metricsByAnchor.set(row.anchor, metrics);
|
|
2066
|
+
return metrics;
|
|
2067
|
+
};
|
|
2068
|
+
for (const row of sorted) {
|
|
2069
|
+
visit(row);
|
|
2070
|
+
}
|
|
2071
|
+
return metricsByAnchor;
|
|
2072
|
+
}
|
|
2073
|
+
normalizeDocumentSummarySourceText(value) {
|
|
2074
|
+
return String(value ?? '')
|
|
2075
|
+
.replace(/<!--[\s\S]*?-->/g, ' ')
|
|
2076
|
+
.replace(/\|\s*[-:]{3,}\s*(?=\||$)/g, ' ')
|
|
2077
|
+
.replace(/\|/g, ' / ')
|
|
2078
|
+
.replace(/\s+/g, ' ')
|
|
2079
|
+
.trim();
|
|
2080
|
+
}
|
|
2081
|
+
buildDeterministicDocumentSummaryText(sourceText, fallbackText) {
|
|
2082
|
+
const fallback = this.normalizeDocumentSummarySourceText(fallbackText);
|
|
2083
|
+
const normalized = this.normalizeDocumentSummarySourceText(sourceText);
|
|
2084
|
+
if (!normalized)
|
|
2085
|
+
return fallback;
|
|
2086
|
+
const sentences = normalized
|
|
2087
|
+
.split(/(?<=[.!?])\s+/)
|
|
2088
|
+
.map((entry) => entry.trim())
|
|
2089
|
+
.filter(Boolean);
|
|
2090
|
+
if (sentences.length === 0) {
|
|
2091
|
+
return this.truncateDocumentSummaryText(normalized, 220);
|
|
2092
|
+
}
|
|
2093
|
+
const selected = [];
|
|
2094
|
+
let length = 0;
|
|
2095
|
+
for (const sentence of sentences) {
|
|
2096
|
+
const nextLength = length === 0 ? sentence.length : length + 1 + sentence.length;
|
|
2097
|
+
if (selected.length > 0 && nextLength > 220)
|
|
2098
|
+
break;
|
|
2099
|
+
selected.push(sentence);
|
|
2100
|
+
length = nextLength;
|
|
2101
|
+
if (length >= 96)
|
|
2102
|
+
break;
|
|
2103
|
+
}
|
|
2104
|
+
const summary = selected.join(' ').trim();
|
|
2105
|
+
if (summary)
|
|
2106
|
+
return this.truncateDocumentSummaryText(summary, 220);
|
|
2107
|
+
return this.truncateDocumentSummaryText(normalized, 220);
|
|
2108
|
+
}
|
|
2109
|
+
truncateDocumentSummaryText(value, maxLength) {
|
|
2110
|
+
const normalized = this.normalizeDocumentSummarySourceText(value);
|
|
2111
|
+
if (normalized.length <= maxLength)
|
|
2112
|
+
return normalized;
|
|
2113
|
+
return `${normalized.slice(0, Math.max(0, maxLength - 3)).trimEnd()}...`;
|
|
2114
|
+
}
|
|
2115
|
+
countDocumentSummaryWords(value) {
|
|
2116
|
+
const normalized = this.normalizeDocumentSummarySourceText(value);
|
|
2117
|
+
if (!normalized)
|
|
2118
|
+
return 0;
|
|
2119
|
+
return normalized.split(/\s+/).filter(Boolean).length;
|
|
2120
|
+
}
|
|
2121
|
+
buildPersistedDocumentPageRanges(pages) {
|
|
2122
|
+
const ranges = new Map();
|
|
2123
|
+
for (const page of pages) {
|
|
2124
|
+
for (const chunk of page.chunks) {
|
|
2125
|
+
const existing = ranges.get(chunk.linkId);
|
|
2126
|
+
if (!existing) {
|
|
2127
|
+
ranges.set(chunk.linkId, { start: page.pageNumber, end: page.pageNumber });
|
|
2128
|
+
continue;
|
|
2129
|
+
}
|
|
2130
|
+
existing.start = Math.min(existing.start, page.pageNumber);
|
|
2131
|
+
existing.end = Math.max(existing.end, page.pageNumber);
|
|
2132
|
+
}
|
|
2133
|
+
}
|
|
2134
|
+
return ranges;
|
|
2135
|
+
}
|
|
2136
|
+
buildDocumentIndexBreadcrumb(title, parentLinkId, breadcrumbByLinkId, documentTitle) {
|
|
2137
|
+
const parentBreadcrumb = parentLinkId ? breadcrumbByLinkId.get(parentLinkId) : undefined;
|
|
2138
|
+
if (parentBreadcrumb)
|
|
2139
|
+
return `${parentBreadcrumb} / ${title}`;
|
|
2140
|
+
return `${documentTitle} / ${title}`;
|
|
2141
|
+
}
|
|
2142
|
+
renderDocumentSourceBundleFromResolved(resolved) {
|
|
2143
|
+
const lines = [];
|
|
2144
|
+
this.prependModulePreambles(lines, this.uniqueModulePreambles(resolved.items
|
|
2145
|
+
.filter((item) => item.kind === 'page')
|
|
2146
|
+
.map((item) => item.modulePreamble)));
|
|
2147
|
+
lines.push(`# ${resolved.title}`);
|
|
2148
|
+
const documentReleaseNotes = this.normalizeNonEmpty(resolved.documentReleaseNotes);
|
|
2149
|
+
if (documentReleaseNotes) {
|
|
2150
|
+
lines.push('');
|
|
2151
|
+
lines.push(`_Release Notes:_ ${documentReleaseNotes}`);
|
|
2152
|
+
}
|
|
2153
|
+
for (const item of resolved.items) {
|
|
2154
|
+
if (item.pageBreakBefore) {
|
|
2155
|
+
lines.push('', '<!-- pagebreak -->');
|
|
2156
|
+
}
|
|
2157
|
+
lines.push('');
|
|
2158
|
+
if (item.kind === 'section') {
|
|
2159
|
+
lines.push(...this.renderSectionHeading(item));
|
|
2160
|
+
}
|
|
2161
|
+
else {
|
|
2162
|
+
lines.push(...this.renderPageBody(item, 2 + item.depth));
|
|
2163
|
+
}
|
|
2164
|
+
if (item.pageBreakAfter) {
|
|
2165
|
+
lines.push('', '<!-- pagebreak -->');
|
|
2166
|
+
}
|
|
2167
|
+
}
|
|
2168
|
+
return {
|
|
2169
|
+
...reduceDocmanComposeFormats(resolved.items
|
|
2170
|
+
.filter((item) => item.kind === 'page')
|
|
2171
|
+
.map((item) => item.format)),
|
|
2172
|
+
content: this.joinMarkdown(lines),
|
|
2173
|
+
assets: this.uniqueAssetRefs(resolved.items.flatMap((item) => (item.kind === 'page' ? item.assetRefs : []))),
|
|
2174
|
+
};
|
|
2175
|
+
}
|
|
2176
|
+
renderSectionSourceBundleFromSection(documentTitle, resolved, sectionItem) {
|
|
2177
|
+
const scopedItems = this.collectSubtreeItems(resolved.items, sectionItem.linkId);
|
|
2178
|
+
const lines = [];
|
|
2179
|
+
this.prependModulePreambles(lines, this.uniqueModulePreambles(scopedItems
|
|
2180
|
+
.filter((item) => item.kind === 'page')
|
|
2181
|
+
.map((item) => item.modulePreamble)));
|
|
2182
|
+
lines.push(`# ${documentTitle}`);
|
|
2183
|
+
for (const item of scopedItems) {
|
|
2184
|
+
lines.push('');
|
|
2185
|
+
if (item.pageBreakBefore) {
|
|
2186
|
+
lines.push('<!-- pagebreak -->', '');
|
|
2187
|
+
}
|
|
2188
|
+
if (item.kind === 'section') {
|
|
2189
|
+
lines.push(...this.renderSectionHeading(item));
|
|
2190
|
+
}
|
|
2191
|
+
else {
|
|
2192
|
+
lines.push(...this.renderPageBody(item, 2 + item.depth));
|
|
2193
|
+
}
|
|
2194
|
+
if (item.pageBreakAfter) {
|
|
2195
|
+
lines.push('', '<!-- pagebreak -->');
|
|
2196
|
+
}
|
|
2197
|
+
}
|
|
2198
|
+
return {
|
|
2199
|
+
...reduceDocmanComposeFormats(scopedItems
|
|
2200
|
+
.filter((item) => item.kind === 'page')
|
|
2201
|
+
.map((item) => item.format)),
|
|
2202
|
+
content: this.joinMarkdown(lines),
|
|
2203
|
+
assets: this.uniqueAssetRefs(scopedItems.flatMap((item) => (item.kind === 'page' ? item.assetRefs : []))),
|
|
2204
|
+
};
|
|
2205
|
+
}
|
|
2206
|
+
renderSinglePageItemSourceBundle(documentTitle, pageItem) {
|
|
2207
|
+
const lines = [];
|
|
2208
|
+
this.prependModulePreambles(lines, this.uniqueModulePreambles([pageItem.modulePreamble]));
|
|
2209
|
+
lines.push(`# ${documentTitle}`, '');
|
|
2210
|
+
lines.push(...this.renderPageBody(pageItem, 2 + pageItem.depth));
|
|
2211
|
+
return {
|
|
2212
|
+
format: pageItem.format,
|
|
2213
|
+
formats: [pageItem.format],
|
|
2214
|
+
content: this.joinMarkdown(lines),
|
|
2215
|
+
assets: this.uniqueAssetRefs(pageItem.assetRefs),
|
|
2216
|
+
};
|
|
2217
|
+
}
|
|
2218
|
+
renderPageSourceBundle(documentTitle, page) {
|
|
2219
|
+
const lines = [];
|
|
2220
|
+
this.prependModulePreambles(lines, page.modulePreambles);
|
|
2221
|
+
lines.push(`# ${documentTitle}`, '', `## Page ${page.pageNumber}`);
|
|
2222
|
+
if (page.chunks.length === 0) {
|
|
2223
|
+
lines.push('', '_No content for this composed page._');
|
|
2224
|
+
return {
|
|
2225
|
+
format: page.format,
|
|
2226
|
+
formats: [...page.formats],
|
|
2227
|
+
content: this.joinMarkdown(lines),
|
|
2228
|
+
assets: [],
|
|
2229
|
+
};
|
|
2230
|
+
}
|
|
2231
|
+
let currentLinkId = null;
|
|
2232
|
+
for (const chunk of page.chunks) {
|
|
2233
|
+
if (currentLinkId !== chunk.linkId) {
|
|
2234
|
+
const headingPrefix = '#'.repeat(Math.max(2, Math.min(6, chunk.depth + 2)));
|
|
2235
|
+
lines.push('');
|
|
2236
|
+
if (chunk.titleVisible) {
|
|
2237
|
+
lines.push(`${headingPrefix} ${chunk.number} ${stripLeadingNumericPrefixForRender(chunk.title, chunk.number)}`);
|
|
2238
|
+
}
|
|
2239
|
+
else {
|
|
2240
|
+
lines.push(`<!-- ${chunk.number} (title hidden) -->`);
|
|
2241
|
+
}
|
|
2242
|
+
currentLinkId = chunk.linkId;
|
|
2243
|
+
}
|
|
2244
|
+
const body = chunk.content.trim();
|
|
2245
|
+
if (body.length > 0) {
|
|
2246
|
+
lines.push('', body);
|
|
2247
|
+
}
|
|
2248
|
+
}
|
|
2249
|
+
return {
|
|
2250
|
+
format: page.format,
|
|
2251
|
+
formats: [...page.formats],
|
|
2252
|
+
content: this.joinMarkdown(lines),
|
|
2253
|
+
assets: this.uniqueAssetRefs(page.assets),
|
|
2254
|
+
};
|
|
2255
|
+
}
|
|
2256
|
+
materializePublishContent(fragment, targetDescriptor) {
|
|
2257
|
+
if (targetDescriptor.target === 'markdown') {
|
|
2258
|
+
return this.materializeMarkdownPublishContent(fragment, targetDescriptor);
|
|
2259
|
+
}
|
|
2260
|
+
return this.materializeHtmlPublishContent(fragment, targetDescriptor);
|
|
2261
|
+
}
|
|
2262
|
+
materializeMarkdownPublishContent(fragment, targetDescriptor) {
|
|
2263
|
+
return {
|
|
2264
|
+
mediaType: targetDescriptor.mediaType,
|
|
2265
|
+
content: fragment.content,
|
|
2266
|
+
warnings: [],
|
|
2267
|
+
};
|
|
2268
|
+
}
|
|
2269
|
+
materializeHtmlPublishContent(fragment, targetDescriptor) {
|
|
2270
|
+
const warnings = [];
|
|
2271
|
+
const sourceParts = splitDocmanComposeSourceContent(fragment.format, fragment.content);
|
|
2272
|
+
if (this.normalizeNonEmpty(sourceParts.modulePreamble)) {
|
|
2273
|
+
this.pushPublishWarning(warnings, 'mdx_module_preamble_omitted', 'MDX import/export preamble is omitted from HTML materialization and is not executed.');
|
|
2274
|
+
}
|
|
2275
|
+
const blocks = this.tokenizeHtmlMaterializationBlocks(sourceParts.body, warnings);
|
|
2276
|
+
const renderedBody = blocks
|
|
2277
|
+
.map((block) => block.kind === 'markdown' ? this.renderMarkdownBlockToHtml(block.content) : block.content)
|
|
2278
|
+
.filter((entry) => this.normalizeNonEmpty(entry))
|
|
2279
|
+
.join('\n');
|
|
2280
|
+
return {
|
|
2281
|
+
mediaType: targetDescriptor.mediaType,
|
|
2282
|
+
content: this.buildPublishedHtmlDocument(this.toPublishedHtmlTitle(fragment), renderedBody),
|
|
2283
|
+
warnings,
|
|
2284
|
+
};
|
|
2285
|
+
}
|
|
2286
|
+
tokenizeHtmlMaterializationBlocks(source, warnings) {
|
|
2287
|
+
const lines = String(source ?? '').split(/\r?\n/);
|
|
2288
|
+
const blocks = [];
|
|
2289
|
+
let markdownLines = [];
|
|
2290
|
+
const flushMarkdown = () => {
|
|
2291
|
+
const content = markdownLines.join('\n').trim();
|
|
2292
|
+
markdownLines = [];
|
|
2293
|
+
if (!content)
|
|
2294
|
+
return;
|
|
2295
|
+
blocks.push({ kind: 'markdown', content });
|
|
2296
|
+
};
|
|
2297
|
+
for (let index = 0; index < lines.length; index += 1) {
|
|
2298
|
+
const line = String(lines[index] ?? '');
|
|
2299
|
+
const trimmed = line.trim();
|
|
2300
|
+
if (!trimmed) {
|
|
2301
|
+
markdownLines.push('');
|
|
2302
|
+
continue;
|
|
2303
|
+
}
|
|
2304
|
+
if (PAGE_BREAK_LINE_RE.test(line)) {
|
|
2305
|
+
flushMarkdown();
|
|
2306
|
+
blocks.push({ kind: 'raw', content: DOCMAN_PAGEBREAK_HTML });
|
|
2307
|
+
this.pushPublishWarning(warnings, 'pagebreak_rendered', 'Compose pagebreak markers are rendered as horizontal separators in HTML output.');
|
|
2308
|
+
continue;
|
|
2309
|
+
}
|
|
2310
|
+
if (!this.isHtmlLikeStandaloneLine(trimmed)) {
|
|
2311
|
+
markdownLines.push(line);
|
|
2312
|
+
continue;
|
|
2313
|
+
}
|
|
2314
|
+
flushMarkdown();
|
|
2315
|
+
const rawLines = [line];
|
|
2316
|
+
while (index + 1 < lines.length) {
|
|
2317
|
+
const nextLine = String(lines[index + 1] ?? '');
|
|
2318
|
+
const nextTrimmed = nextLine.trim();
|
|
2319
|
+
if (!nextTrimmed || !this.isHtmlLikeStandaloneLine(nextTrimmed) || PAGE_BREAK_LINE_RE.test(nextLine)) {
|
|
2320
|
+
break;
|
|
2321
|
+
}
|
|
2322
|
+
rawLines.push(nextLine);
|
|
2323
|
+
index += 1;
|
|
2324
|
+
}
|
|
2325
|
+
const rawBlock = rawLines.join('\n').trim();
|
|
2326
|
+
if (!rawBlock)
|
|
2327
|
+
continue;
|
|
2328
|
+
blocks.push({ kind: 'raw', content: rawBlock });
|
|
2329
|
+
if (!HTML_COMMENT_LINE_RE.test(trimmed)) {
|
|
2330
|
+
this.pushPublishWarning(warnings, 'raw_html_block_preserved', 'Standalone HTML/JSX blocks are preserved literally in HTML materialization.');
|
|
2331
|
+
}
|
|
2332
|
+
}
|
|
2333
|
+
flushMarkdown();
|
|
2334
|
+
return blocks;
|
|
2335
|
+
}
|
|
2336
|
+
renderMarkdownBlockToHtml(markdown) {
|
|
2337
|
+
const content = String(markdown ?? '').trim();
|
|
2338
|
+
if (!content)
|
|
2339
|
+
return '';
|
|
2340
|
+
return renderToStaticMarkup(createElement(ReactMarkdown, {
|
|
2341
|
+
remarkPlugins: [remarkGfm],
|
|
2342
|
+
}, content));
|
|
2343
|
+
}
|
|
2344
|
+
buildPublishedHtmlDocument(title, bodyHtml) {
|
|
2345
|
+
const body = this.normalizeNonEmpty(bodyHtml) ?? '<p><em>No content to materialize.</em></p>';
|
|
2346
|
+
return [
|
|
2347
|
+
'<!doctype html>',
|
|
2348
|
+
'<html lang="en">',
|
|
2349
|
+
'<head>',
|
|
2350
|
+
' <meta charset="utf-8" />',
|
|
2351
|
+
' <meta name="viewport" content="width=device-width, initial-scale=1" />',
|
|
2352
|
+
` <title>${this.escapeHtml(title)}</title>`,
|
|
2353
|
+
` <style>${DOCMAN_PUBLISH_HTML_STYLES}</style>`,
|
|
2354
|
+
'</head>',
|
|
2355
|
+
'<body>',
|
|
2356
|
+
' <main>',
|
|
2357
|
+
body
|
|
2358
|
+
.split('\n')
|
|
2359
|
+
.map((line) => ` ${line}`)
|
|
2360
|
+
.join('\n'),
|
|
2361
|
+
' </main>',
|
|
2362
|
+
'</body>',
|
|
2363
|
+
'</html>',
|
|
2364
|
+
].join('\n');
|
|
2365
|
+
}
|
|
2366
|
+
toPublishedHtmlTitle(fragment) {
|
|
2367
|
+
const lines = String(fragment.content ?? '').split(/\r?\n/);
|
|
2368
|
+
for (const line of lines) {
|
|
2369
|
+
const trimmed = line.trim();
|
|
2370
|
+
if (trimmed.startsWith('# ')) {
|
|
2371
|
+
return trimmed.slice(2).trim() || 'Docman Document';
|
|
2372
|
+
}
|
|
2373
|
+
}
|
|
2374
|
+
if (fragment.kind === 'page' && fragment.pageNumber) {
|
|
2375
|
+
return `Document page ${fragment.pageNumber}`;
|
|
2376
|
+
}
|
|
2377
|
+
if (fragment.kind === 'section' && fragment.sectionId) {
|
|
2378
|
+
return `Document section ${fragment.sectionId}`;
|
|
2379
|
+
}
|
|
2380
|
+
if (fragment.kind === 'page' && fragment.pageVersionId) {
|
|
2381
|
+
return `Document page ${fragment.pageVersionId}`;
|
|
2382
|
+
}
|
|
2383
|
+
return 'Docman Document';
|
|
2384
|
+
}
|
|
2385
|
+
isHtmlLikeStandaloneLine(value) {
|
|
2386
|
+
const normalized = String(value ?? '').trim();
|
|
2387
|
+
if (!normalized)
|
|
2388
|
+
return false;
|
|
2389
|
+
return HTML_COMMENT_LINE_RE.test(normalized) || HTML_LIKE_BLOCK_LINE_RE.test(normalized);
|
|
2390
|
+
}
|
|
2391
|
+
pushPublishWarning(warnings, code, message) {
|
|
2392
|
+
if (warnings.some((warning) => warning.code === code))
|
|
2393
|
+
return;
|
|
2394
|
+
warnings.push({ code, message });
|
|
2395
|
+
}
|
|
2396
|
+
renderSectionHeading(item) {
|
|
2397
|
+
const headingLevel = Math.max(2, Math.min(6, 2 + item.depth));
|
|
2398
|
+
const headingPrefix = '#'.repeat(headingLevel);
|
|
2399
|
+
return [`${headingPrefix} ${item.number} ${stripLeadingNumericPrefixForRender(item.title, item.number)}`];
|
|
2400
|
+
}
|
|
2401
|
+
renderPageBody(item, headingLevel) {
|
|
2402
|
+
const lines = [];
|
|
2403
|
+
const headingPrefix = '#'.repeat(Math.max(2, Math.min(6, headingLevel)));
|
|
2404
|
+
if (item.titleVisible) {
|
|
2405
|
+
lines.push(`${headingPrefix} ${item.number} ${stripLeadingNumericPrefixForRender(item.title, item.number)}`);
|
|
2406
|
+
}
|
|
2407
|
+
else {
|
|
2408
|
+
lines.push(`<!-- ${item.number} (title hidden) -->`);
|
|
2409
|
+
}
|
|
2410
|
+
const parts = item.contentParts.length > 0 ? item.contentParts : [''];
|
|
2411
|
+
for (let i = 0; i < parts.length; i++) {
|
|
2412
|
+
const body = (parts[i] ?? '').trim();
|
|
2413
|
+
if (body.length > 0) {
|
|
2414
|
+
lines.push('', body);
|
|
2415
|
+
}
|
|
2416
|
+
if (i < parts.length - 1) {
|
|
2417
|
+
lines.push('', '<!-- pagebreak -->');
|
|
2418
|
+
}
|
|
2419
|
+
}
|
|
2420
|
+
return lines;
|
|
2421
|
+
}
|
|
2422
|
+
splitContentByPageBreakMarkers(content) {
|
|
2423
|
+
const text = String(content ?? '');
|
|
2424
|
+
if (!text)
|
|
2425
|
+
return [''];
|
|
2426
|
+
const lines = text.split(/\r?\n/);
|
|
2427
|
+
const parts = [];
|
|
2428
|
+
let current = [];
|
|
2429
|
+
for (const line of lines) {
|
|
2430
|
+
if (PAGE_BREAK_LINE_RE.test(line)) {
|
|
2431
|
+
parts.push(current.join('\n').trim());
|
|
2432
|
+
current = [];
|
|
2433
|
+
}
|
|
2434
|
+
else {
|
|
2435
|
+
current.push(line);
|
|
2436
|
+
}
|
|
2437
|
+
}
|
|
2438
|
+
parts.push(current.join('\n').trim());
|
|
2439
|
+
return parts.length > 0 ? parts : [''];
|
|
2440
|
+
}
|
|
2441
|
+
collectSubtreeItems(items, rootLinkId) {
|
|
2442
|
+
const byId = new Map();
|
|
2443
|
+
for (const item of items) {
|
|
2444
|
+
byId.set(item.linkId, item);
|
|
2445
|
+
}
|
|
2446
|
+
const isDescendant = (candidate) => {
|
|
2447
|
+
if (candidate.linkId === rootLinkId)
|
|
2448
|
+
return true;
|
|
2449
|
+
let cursor = candidate.parentLinkId;
|
|
2450
|
+
while (cursor) {
|
|
2451
|
+
if (cursor === rootLinkId)
|
|
2452
|
+
return true;
|
|
2453
|
+
const parent = byId.get(cursor);
|
|
2454
|
+
if (!parent)
|
|
2455
|
+
return false;
|
|
2456
|
+
cursor = parent.parentLinkId;
|
|
2457
|
+
}
|
|
2458
|
+
return false;
|
|
2459
|
+
};
|
|
2460
|
+
return items.filter((item) => isDescendant(item));
|
|
2461
|
+
}
|
|
2462
|
+
resolveDocumentTitle(document, documentVersion, localeState) {
|
|
2463
|
+
const versionTitle = this.normalizeNonEmpty(documentVersion.title);
|
|
2464
|
+
if (versionTitle)
|
|
2465
|
+
return versionTitle;
|
|
2466
|
+
return this.resolveLocalizedValue(document.titleMl, localeState, document.title);
|
|
2467
|
+
}
|
|
2468
|
+
resolveSectionTitle(section, titleOverride, localeState) {
|
|
2469
|
+
const override = this.normalizeNonEmpty(titleOverride);
|
|
2470
|
+
if (override)
|
|
2471
|
+
return override;
|
|
2472
|
+
const title = this.resolveLocalizedValue(section.titleMl, localeState, section.title);
|
|
2473
|
+
if (title)
|
|
2474
|
+
return title;
|
|
2475
|
+
return 'Section';
|
|
2476
|
+
}
|
|
2477
|
+
resolvePageTitle(page, pageVersion, titleOverride, localeState) {
|
|
2478
|
+
const override = this.normalizeNonEmpty(titleOverride);
|
|
2479
|
+
if (override)
|
|
2480
|
+
return override;
|
|
2481
|
+
const versionTitle = this.normalizeNonEmpty(pageVersion.title);
|
|
2482
|
+
if (versionTitle)
|
|
2483
|
+
return versionTitle;
|
|
2484
|
+
if (page) {
|
|
2485
|
+
const pageTitle = this.resolveLocalizedValue(page.titleMl, localeState, page.title);
|
|
2486
|
+
if (pageTitle)
|
|
2487
|
+
return pageTitle;
|
|
2488
|
+
}
|
|
2489
|
+
return `Page ${pageVersion.version}`;
|
|
2490
|
+
}
|
|
2491
|
+
resolvePageContent(pageVersion, localeState) {
|
|
2492
|
+
return this.resolveLocalizedValue(pageVersion.contentMl, localeState, pageVersion.content ?? '');
|
|
2493
|
+
}
|
|
2494
|
+
resolveComposePageSource(pageVersion, localeState, stage, operation) {
|
|
2495
|
+
return Effect.gen(this, function* (_) {
|
|
2496
|
+
const format = resolveDocmanComposeSourceFormat(pageVersion.format);
|
|
2497
|
+
if (!format) {
|
|
2498
|
+
return yield* _(Effect.fail(XfErrorFactory.upsertFailed({
|
|
2499
|
+
stage,
|
|
2500
|
+
operation,
|
|
2501
|
+
message: 'unsupported_compose_source_format',
|
|
2502
|
+
data: {
|
|
2503
|
+
pageVersionId: pageVersion.id,
|
|
2504
|
+
format: pageVersion.format,
|
|
2505
|
+
supportedFormats: ['md', 'mdx'],
|
|
2506
|
+
},
|
|
2507
|
+
})));
|
|
2508
|
+
}
|
|
2509
|
+
const source = normalizeDocmanComposeSourceContent(format, this.resolvePageContent(pageVersion, localeState));
|
|
2510
|
+
const assetTokens = listDocmanAssetReferenceTokens(source);
|
|
2511
|
+
if (assetTokens.length === 0) {
|
|
2512
|
+
const sourceParts = splitDocmanComposeSourceContent(format, source);
|
|
2513
|
+
return {
|
|
2514
|
+
format,
|
|
2515
|
+
modulePreamble: sourceParts.modulePreamble,
|
|
2516
|
+
content: sourceParts.body,
|
|
2517
|
+
assets: [],
|
|
2518
|
+
};
|
|
2519
|
+
}
|
|
2520
|
+
const assetRefs = yield* _(Effect.all(assetTokens.map((token) => this.resolveAssetReferenceToken(token, stage, `${operation}.asset`))));
|
|
2521
|
+
const byToken = new Map(assetRefs.map((assetRef) => [assetRef.token, assetRef]));
|
|
2522
|
+
const resolvedSource = replaceDocmanAssetReferenceTokens(source, (token) => byToken.get(token.token)?.href ?? token.token);
|
|
2523
|
+
const sourceParts = splitDocmanComposeSourceContent(format, resolvedSource);
|
|
2524
|
+
return {
|
|
2525
|
+
format,
|
|
2526
|
+
modulePreamble: sourceParts.modulePreamble,
|
|
2527
|
+
content: sourceParts.body,
|
|
2528
|
+
assets: this.uniqueAssetRefs(assetRefs),
|
|
2529
|
+
};
|
|
2530
|
+
});
|
|
2531
|
+
}
|
|
2532
|
+
resolveVersionReleaseNotes(version, localeState) {
|
|
2533
|
+
const fallback = this.normalizeNonEmpty(version.releaseNotes);
|
|
2534
|
+
const localized = this.resolveLocalizedValue((version.releaseNotesMl ?? undefined), localeState, fallback ?? '');
|
|
2535
|
+
return this.normalizeNonEmpty(localized) ?? fallback;
|
|
2536
|
+
}
|
|
2537
|
+
resolveAssetReferenceToken(token, stage, operation) {
|
|
2538
|
+
return Effect.gen(this, function* (_) {
|
|
2539
|
+
const assetRepository = yield* _(this.requireDependency(this.assetRepository, 'assetRepository', stage, operation));
|
|
2540
|
+
const assetVersionRepository = yield* _(this.requireDependency(this.assetVersionRepository, 'assetVersionRepository', stage, operation));
|
|
2541
|
+
let asset = null;
|
|
2542
|
+
const assetsByUid = yield* _(assetRepository
|
|
2543
|
+
.find({
|
|
2544
|
+
matchEq: { assetUid: token.ref },
|
|
2545
|
+
options: { limit: 1 },
|
|
2546
|
+
})
|
|
2547
|
+
.pipe(Effect.mapError(mapDbError({ stage, operation: 'assetRepository.find(assetUid)', factory: XfErrorFactory.notFound }))));
|
|
2548
|
+
asset = assetsByUid[0] ?? null;
|
|
2549
|
+
if (!asset) {
|
|
2550
|
+
const assetsBySlug = yield* _(assetRepository
|
|
2551
|
+
.find({
|
|
2552
|
+
matchEq: { slug: token.ref },
|
|
2553
|
+
options: { limit: 1 },
|
|
2554
|
+
})
|
|
2555
|
+
.pipe(Effect.mapError(mapDbError({ stage, operation: 'assetRepository.find(slug)', factory: XfErrorFactory.notFound }))));
|
|
2556
|
+
asset = assetsBySlug[0] ?? null;
|
|
2557
|
+
}
|
|
2558
|
+
if (!asset?.id) {
|
|
2559
|
+
return yield* _(Effect.fail(XfErrorFactory.notFound({
|
|
2560
|
+
stage,
|
|
2561
|
+
operation,
|
|
2562
|
+
message: 'Asset reference could not be resolved.',
|
|
2563
|
+
identifier: { token: token.token, ref: token.ref },
|
|
2564
|
+
})));
|
|
2565
|
+
}
|
|
2566
|
+
let assetVersion = null;
|
|
2567
|
+
if (token.version !== null) {
|
|
2568
|
+
const versions = yield* _(assetVersionRepository
|
|
2569
|
+
.find({
|
|
2570
|
+
matchEq: { assetId: asset.id, version: token.version },
|
|
2571
|
+
options: { limit: 1 },
|
|
2572
|
+
})
|
|
2573
|
+
.pipe(Effect.mapError(mapDbError({
|
|
2574
|
+
stage,
|
|
2575
|
+
operation: 'assetVersionRepository.find(version)',
|
|
2576
|
+
factory: XfErrorFactory.notFound,
|
|
2577
|
+
}))));
|
|
2578
|
+
assetVersion = versions[0] ?? null;
|
|
2579
|
+
}
|
|
2580
|
+
else if (this.normalizeNonEmpty(asset.currentVersionId)) {
|
|
2581
|
+
assetVersion = yield* _(assetVersionRepository.findById(String(asset.currentVersionId)).pipe(Effect.mapError(mapDbError({
|
|
2582
|
+
stage,
|
|
2583
|
+
operation: 'assetVersionRepository.findById(currentVersionId)',
|
|
2584
|
+
factory: XfErrorFactory.notFound,
|
|
2585
|
+
}))));
|
|
2586
|
+
}
|
|
2587
|
+
if (!assetVersion) {
|
|
2588
|
+
const versions = yield* _(assetVersionRepository
|
|
2589
|
+
.find({
|
|
2590
|
+
matchEq: { assetId: asset.id },
|
|
2591
|
+
options: { sort: [{ field: 'version', type: 'desc' }], limit: 50 },
|
|
2592
|
+
})
|
|
2593
|
+
.pipe(Effect.mapError(mapDbError({
|
|
2594
|
+
stage,
|
|
2595
|
+
operation: 'assetVersionRepository.find(latest)',
|
|
2596
|
+
factory: XfErrorFactory.notFound,
|
|
2597
|
+
}))));
|
|
2598
|
+
assetVersion =
|
|
2599
|
+
versions.find((entry) => this.normalizeNonEmpty(entry.status) === 'ready') ??
|
|
2600
|
+
versions[0] ??
|
|
2601
|
+
null;
|
|
2602
|
+
}
|
|
2603
|
+
if (!assetVersion?.id) {
|
|
2604
|
+
return yield* _(Effect.fail(XfErrorFactory.notFound({
|
|
2605
|
+
stage,
|
|
2606
|
+
operation,
|
|
2607
|
+
message: 'Asset version could not be resolved.',
|
|
2608
|
+
identifier: { token: token.token, assetId: asset.id, version: token.version ?? asset.currentVersionId },
|
|
2609
|
+
})));
|
|
2610
|
+
}
|
|
2611
|
+
const href = this.resolveAssetHref(assetVersion);
|
|
2612
|
+
if (!href) {
|
|
2613
|
+
return yield* _(Effect.fail(XfErrorFactory.upsertFailed({
|
|
2614
|
+
stage,
|
|
2615
|
+
operation,
|
|
2616
|
+
message: 'asset_reference_missing_source_url',
|
|
2617
|
+
data: { token: token.token, assetId: asset.id, assetVersionId: assetVersion.id },
|
|
2618
|
+
})));
|
|
2619
|
+
}
|
|
2620
|
+
return {
|
|
2621
|
+
token: token.token,
|
|
2622
|
+
ref: token.ref,
|
|
2623
|
+
assetId: asset.id,
|
|
2624
|
+
assetVersionId: assetVersion.id,
|
|
2625
|
+
assetVersion: Number(assetVersion.version) || 0,
|
|
2626
|
+
assetUid: this.normalizeNonEmpty(asset.assetUid),
|
|
2627
|
+
slug: this.normalizeNonEmpty(asset.slug),
|
|
2628
|
+
title: this.normalizeNonEmpty(asset.title),
|
|
2629
|
+
altText: this.normalizeNonEmpty(asset.altText),
|
|
2630
|
+
kind: String(asset.kind ?? ''),
|
|
2631
|
+
mime: String(assetVersion.mime ?? ''),
|
|
2632
|
+
href,
|
|
2633
|
+
width: Number.isFinite(Number(assetVersion.width)) ? Number(assetVersion.width) : undefined,
|
|
2634
|
+
height: Number.isFinite(Number(assetVersion.height)) ? Number(assetVersion.height) : undefined,
|
|
2635
|
+
};
|
|
2636
|
+
});
|
|
2637
|
+
}
|
|
2638
|
+
resolveAssetHref(assetVersion) {
|
|
2639
|
+
return this.normalizeNonEmpty(assetVersion.sourceUrl);
|
|
2640
|
+
}
|
|
2641
|
+
uniqueAssetRefs(assetRefs) {
|
|
2642
|
+
const unique = new Map();
|
|
2643
|
+
for (const assetRef of assetRefs) {
|
|
2644
|
+
const key = `${assetRef.assetVersionId}:${assetRef.token}`;
|
|
2645
|
+
if (!unique.has(key))
|
|
2646
|
+
unique.set(key, assetRef);
|
|
2647
|
+
}
|
|
2648
|
+
return [...unique.values()];
|
|
2649
|
+
}
|
|
2650
|
+
uniqueModulePreambles(values) {
|
|
2651
|
+
const unique = new Map();
|
|
2652
|
+
for (const value of values) {
|
|
2653
|
+
const normalized = this.normalizeNonEmpty(value);
|
|
2654
|
+
if (!normalized || unique.has(normalized))
|
|
2655
|
+
continue;
|
|
2656
|
+
unique.set(normalized, normalized);
|
|
2657
|
+
}
|
|
2658
|
+
return [...unique.values()];
|
|
2659
|
+
}
|
|
2660
|
+
prependModulePreambles(lines, modulePreambles) {
|
|
2661
|
+
if (modulePreambles.length === 0)
|
|
2662
|
+
return;
|
|
2663
|
+
lines.push(modulePreambles.join('\n\n'), '');
|
|
2664
|
+
}
|
|
2665
|
+
stripDocumentListOptions(options) {
|
|
2666
|
+
const { includeVersionInfo: _includeVersionInfo, locale: _locale, fallbackLocale: _fallbackLocale, ...query } = options ?? {};
|
|
2667
|
+
return Object.keys(query).length > 0 ? query : undefined;
|
|
2668
|
+
}
|
|
2669
|
+
resolveLocaleOptions(options) {
|
|
2670
|
+
return {
|
|
2671
|
+
locale: this.normalizeLocale(options?.locale ?? this.locale),
|
|
2672
|
+
fallbackLocale: this.normalizeLocale(options?.fallbackLocale),
|
|
2673
|
+
};
|
|
2674
|
+
}
|
|
2675
|
+
normalizeLocale(value) {
|
|
2676
|
+
if (typeof value !== 'string')
|
|
2677
|
+
return undefined;
|
|
2678
|
+
const normalized = value.trim().toLowerCase();
|
|
2679
|
+
return normalized.length > 0 ? normalized : undefined;
|
|
2680
|
+
}
|
|
2681
|
+
normalizeNonEmpty(value) {
|
|
2682
|
+
if (typeof value !== 'string')
|
|
2683
|
+
return undefined;
|
|
2684
|
+
const trimmed = value.trim();
|
|
2685
|
+
return trimmed.length > 0 ? trimmed : undefined;
|
|
2686
|
+
}
|
|
2687
|
+
escapeHtml(value) {
|
|
2688
|
+
return String(value ?? '')
|
|
2689
|
+
.replaceAll('&', '&')
|
|
2690
|
+
.replaceAll('<', '<')
|
|
2691
|
+
.replaceAll('>', '>')
|
|
2692
|
+
.replaceAll('"', '"')
|
|
2693
|
+
.replaceAll("'", ''');
|
|
2694
|
+
}
|
|
2695
|
+
resolveLocalizedValue(value, localeState, fallbackText = '') {
|
|
2696
|
+
if (!value)
|
|
2697
|
+
return fallbackText;
|
|
2698
|
+
const locale = this.normalizeLocale(localeState.locale);
|
|
2699
|
+
if (locale) {
|
|
2700
|
+
const exact = value[locale];
|
|
2701
|
+
if (typeof exact === 'string' && exact.trim().length > 0)
|
|
2702
|
+
return exact;
|
|
2703
|
+
}
|
|
2704
|
+
const fallbackLocale = this.normalizeLocale(localeState.fallbackLocale);
|
|
2705
|
+
if (fallbackLocale) {
|
|
2706
|
+
const fallback = value[fallbackLocale];
|
|
2707
|
+
if (typeof fallback === 'string' && fallback.trim().length > 0)
|
|
2708
|
+
return fallback;
|
|
2709
|
+
}
|
|
2710
|
+
const first = Object.values(value).find((entry) => typeof entry === 'string' && entry.trim().length > 0);
|
|
2711
|
+
return first ?? fallbackText;
|
|
2712
|
+
}
|
|
2713
|
+
buildLanguageCandidates(localeState) {
|
|
2714
|
+
const candidates = [];
|
|
2715
|
+
const push = (value) => {
|
|
2716
|
+
const normalized = this.normalizeLocale(value);
|
|
2717
|
+
if (!normalized)
|
|
2718
|
+
return;
|
|
2719
|
+
if (!candidates.includes(normalized))
|
|
2720
|
+
candidates.push(normalized);
|
|
2721
|
+
};
|
|
2722
|
+
push(localeState.locale);
|
|
2723
|
+
push(localeState.fallbackLocale);
|
|
2724
|
+
return candidates;
|
|
2725
|
+
}
|
|
2726
|
+
withLocaleOptions(options, localeState, mlgFields) {
|
|
2727
|
+
const projectionOptions = { ...(options?.projectionOptions ?? {}) };
|
|
2728
|
+
const hasLanguages = Array.isArray(projectionOptions.languages) && projectionOptions.languages.length > 0;
|
|
2729
|
+
const languages = this.buildLanguageCandidates(localeState);
|
|
2730
|
+
if (!hasLanguages && languages.length > 0) {
|
|
2731
|
+
projectionOptions.languages = languages;
|
|
2732
|
+
}
|
|
2733
|
+
const hasDefaultLocaleSelection = hasLanguages || languages.length > 0;
|
|
2734
|
+
if (!options && !hasDefaultLocaleSelection) {
|
|
2735
|
+
return undefined;
|
|
2736
|
+
}
|
|
2737
|
+
const mlgFieldsResolved = options?.mlgFields && options.mlgFields.length > 0 ? options.mlgFields : [...mlgFields];
|
|
2738
|
+
return {
|
|
2739
|
+
...(options ?? {}),
|
|
2740
|
+
mlgFields: mlgFieldsResolved,
|
|
2741
|
+
projectionOptions,
|
|
2742
|
+
};
|
|
2743
|
+
}
|
|
2744
|
+
traverseLinks(links, parentNumber, baseDepth) {
|
|
2745
|
+
if (!Array.isArray(links) || links.length === 0)
|
|
2746
|
+
return [];
|
|
2747
|
+
const byId = new Map();
|
|
2748
|
+
for (const link of links) {
|
|
2749
|
+
if (link.id) {
|
|
2750
|
+
byId.set(link.id, link);
|
|
2751
|
+
}
|
|
2752
|
+
}
|
|
2753
|
+
const childrenByParent = new Map();
|
|
2754
|
+
const roots = [];
|
|
2755
|
+
for (const link of links) {
|
|
2756
|
+
const parentId = this.normalizeNonEmpty(link.parentLinkId);
|
|
2757
|
+
if (!parentId || !byId.has(parentId)) {
|
|
2758
|
+
roots.push(link);
|
|
2759
|
+
continue;
|
|
2760
|
+
}
|
|
2761
|
+
const arr = childrenByParent.get(parentId) ?? [];
|
|
2762
|
+
arr.push(link);
|
|
2763
|
+
childrenByParent.set(parentId, arr);
|
|
2764
|
+
}
|
|
2765
|
+
const sortByPosition = (values) => [...values].sort((a, b) => {
|
|
2766
|
+
const posDelta = Number(a.position ?? 0) - Number(b.position ?? 0);
|
|
2767
|
+
if (posDelta !== 0)
|
|
2768
|
+
return posDelta;
|
|
2769
|
+
return String(a.id).localeCompare(String(b.id));
|
|
2770
|
+
});
|
|
2771
|
+
const visited = new Set();
|
|
2772
|
+
const result = [];
|
|
2773
|
+
const walk = (nodes, nodeParentNumber, depth) => {
|
|
2774
|
+
let ordinal = 1;
|
|
2775
|
+
for (const node of sortByPosition(nodes)) {
|
|
2776
|
+
const nodeId = node.id;
|
|
2777
|
+
if (!nodeId)
|
|
2778
|
+
continue;
|
|
2779
|
+
if (visited.has(nodeId))
|
|
2780
|
+
continue;
|
|
2781
|
+
visited.add(nodeId);
|
|
2782
|
+
const number = this.resolveNumber(nodeParentNumber, node.numbering, ordinal);
|
|
2783
|
+
const effectiveDepth = Number.isInteger(node.depth) ? Number(node.depth) : depth;
|
|
2784
|
+
result.push({
|
|
2785
|
+
link: node,
|
|
2786
|
+
depth: effectiveDepth,
|
|
2787
|
+
number,
|
|
2788
|
+
});
|
|
2789
|
+
const children = childrenByParent.get(nodeId) ?? [];
|
|
2790
|
+
walk(children, number, depth + 1);
|
|
2791
|
+
ordinal += 1;
|
|
2792
|
+
}
|
|
2793
|
+
};
|
|
2794
|
+
walk(roots, parentNumber, baseDepth);
|
|
2795
|
+
for (const link of sortByPosition(links)) {
|
|
2796
|
+
if (!link.id || visited.has(link.id))
|
|
2797
|
+
continue;
|
|
2798
|
+
walk([link], parentNumber, baseDepth);
|
|
2799
|
+
}
|
|
2800
|
+
return result;
|
|
2801
|
+
}
|
|
2802
|
+
resolveNumber(parentNumber, explicit, ordinal) {
|
|
2803
|
+
const normalized = this.normalizeNonEmpty(explicit);
|
|
2804
|
+
if (normalized) {
|
|
2805
|
+
if (!parentNumber)
|
|
2806
|
+
return normalized;
|
|
2807
|
+
if (normalized.startsWith(`${parentNumber}.`)) {
|
|
2808
|
+
return normalized;
|
|
2809
|
+
}
|
|
2810
|
+
return `${parentNumber}.${normalized}`;
|
|
2811
|
+
}
|
|
2812
|
+
const segment = String(ordinal);
|
|
2813
|
+
return parentNumber ? `${parentNumber}.${segment}` : segment;
|
|
2814
|
+
}
|
|
2815
|
+
pushUnique(target, value) {
|
|
2816
|
+
if (!target.includes(value)) {
|
|
2817
|
+
target.push(value);
|
|
2818
|
+
}
|
|
2819
|
+
}
|
|
2820
|
+
joinMarkdown(lines) {
|
|
2821
|
+
const merged = lines.join('\n').replace(/\n{3,}/g, '\n\n').trim();
|
|
2822
|
+
return merged.length > 0 ? `${merged}\n` : '';
|
|
2823
|
+
}
|
|
2824
|
+
resolveCascadeDependencies(stage, operation) {
|
|
2825
|
+
return Effect.gen(this, function* (_) {
|
|
2826
|
+
return {
|
|
2827
|
+
documentRepository: this.documentRepository,
|
|
2828
|
+
documentVersionRepository: yield* _(this.requireDependency(this.documentVersionRepository, 'documentVersionRepository', stage, operation)),
|
|
2829
|
+
documentSectionLinkRepository: yield* _(this.requireDependency(this.documentSectionLinkRepository, 'documentSectionLinkRepository', stage, operation)),
|
|
2830
|
+
sectionRepository: yield* _(this.requireDependency(this.sectionRepository, 'sectionRepository', stage, operation)),
|
|
2831
|
+
pageRepository: yield* _(this.requireDependency(this.pageRepository, 'pageRepository', stage, operation)),
|
|
2832
|
+
pageVersionRepository: yield* _(this.requireDependency(this.pageVersionRepository, 'pageVersionRepository', stage, operation)),
|
|
2833
|
+
sectionPageLinkRepository: yield* _(this.requireDependency(this.sectionPageLinkRepository, 'sectionPageLinkRepository', stage, operation)),
|
|
2834
|
+
pageSnippetLinkRepository: yield* _(this.requireDependency(this.pageSnippetLinkRepository, 'pageSnippetLinkRepository', stage, operation)),
|
|
2835
|
+
pageEmbedLinkRepository: yield* _(this.requireDependency(this.pageEmbedLinkRepository, 'pageEmbedLinkRepository', stage, operation)),
|
|
2836
|
+
};
|
|
2837
|
+
});
|
|
2838
|
+
}
|
|
2839
|
+
requireDependency(dependency, name, stage, operation) {
|
|
2840
|
+
if (dependency)
|
|
2841
|
+
return Effect.succeed(dependency);
|
|
2842
|
+
return Effect.fail(XfErrorFactory.configurationError({
|
|
2843
|
+
stage,
|
|
2844
|
+
operation,
|
|
2845
|
+
message: `Missing dependency: ${name}`,
|
|
2846
|
+
debug: { dependency: name },
|
|
2847
|
+
}));
|
|
2848
|
+
}
|
|
2849
|
+
normalizeCascadeError(stage, operation, cause) {
|
|
2850
|
+
if (cause && typeof cause === 'object' && '_tag' in cause) {
|
|
2851
|
+
return cause;
|
|
2852
|
+
}
|
|
2853
|
+
return XfErrorFactory.upsertFailed({
|
|
2854
|
+
stage,
|
|
2855
|
+
operation,
|
|
2856
|
+
message: 'document_cascade_delete_failed',
|
|
2857
|
+
cause,
|
|
2858
|
+
});
|
|
2859
|
+
}
|
|
2860
|
+
}
|