@harperfast/harper 5.3.0-beta.4 → 5.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/bin/backup.ts +6 -1
- package/components/Application.ts +486 -115
- package/components/DESIGN.md +109 -21
- package/components/deploymentProvenance.ts +28 -0
- package/components/operations.js +14 -7
- package/components/packageComponent.ts +5 -1
- package/config-root.schema.json +1 -1
- package/dataLayer/DESIGN.md +35 -7
- package/dataLayer/blobBackup.ts +71 -30
- package/dataLayer/harperBridge/ResourceBridge.ts +16 -2
- package/dataLayer/rocksdbBackup.ts +88 -34
- package/dataLayer/schemaDescribe.ts +45 -1
- package/dist/bin/backup.js +1 -1
- package/dist/bin/backup.js.map +1 -1
- package/dist/components/Application.d.ts +11 -21
- package/dist/components/Application.js +436 -105
- package/dist/components/Application.js.map +1 -1
- package/dist/components/deploymentProvenance.d.ts +4 -0
- package/dist/components/deploymentProvenance.js +32 -0
- package/dist/components/deploymentProvenance.js.map +1 -0
- package/dist/components/operations.js +14 -7
- package/dist/components/operations.js.map +1 -1
- package/dist/components/packageComponent.js +6 -1
- package/dist/components/packageComponent.js.map +1 -1
- package/dist/dataLayer/blobBackup.d.ts +30 -3
- package/dist/dataLayer/blobBackup.js +73 -36
- package/dist/dataLayer/blobBackup.js.map +1 -1
- package/dist/dataLayer/harperBridge/ResourceBridge.d.ts +1 -0
- package/dist/dataLayer/harperBridge/ResourceBridge.js +15 -3
- package/dist/dataLayer/harperBridge/ResourceBridge.js.map +1 -1
- package/dist/dataLayer/rocksdbBackup.d.ts +4 -1
- package/dist/dataLayer/rocksdbBackup.js +74 -36
- package/dist/dataLayer/rocksdbBackup.js.map +1 -1
- package/dist/dataLayer/schemaDescribe.js +45 -1
- package/dist/dataLayer/schemaDescribe.js.map +1 -1
- package/dist/resources/DatabaseTransaction.d.ts +1 -0
- package/dist/resources/DatabaseTransaction.js +33 -8
- package/dist/resources/DatabaseTransaction.js.map +1 -1
- package/dist/resources/ResourceInterface.d.ts +7 -3
- package/dist/resources/ResourceInterface.js +2 -0
- package/dist/resources/ResourceInterface.js.map +1 -1
- package/dist/resources/Table.d.ts +151 -445
- package/dist/resources/Table.js +209 -8
- package/dist/resources/Table.js.map +1 -1
- package/dist/resources/analytics/write.d.ts +11 -0
- package/dist/resources/analytics/write.js +3 -0
- package/dist/resources/analytics/write.js.map +1 -1
- package/dist/resources/blob.d.ts +30 -0
- package/dist/resources/blob.js +189 -1
- package/dist/resources/blob.js.map +1 -1
- package/dist/resources/databases.d.ts +1 -0
- package/dist/resources/databases.js +80 -15
- package/dist/resources/databases.js.map +1 -1
- package/dist/resources/derivedIndexRuntime.d.ts +13 -0
- package/dist/resources/derivedIndexRuntime.js +57 -5
- package/dist/resources/derivedIndexRuntime.js.map +1 -1
- package/dist/resources/derivedIndexes.d.ts +1 -0
- package/dist/resources/derivedIndexes.js +116 -15
- package/dist/resources/derivedIndexes.js.map +1 -1
- package/dist/resources/fullTextSchema.d.ts +5 -3
- package/dist/resources/fullTextSchema.js +48 -5
- package/dist/resources/fullTextSchema.js.map +1 -1
- package/dist/resources/fullTextSchemaLifecycle.d.ts +2 -0
- package/dist/resources/fullTextSchemaLifecycle.js +33 -0
- package/dist/resources/fullTextSchemaLifecycle.js.map +1 -1
- package/dist/resources/graphql.js +30 -12
- package/dist/resources/graphql.js.map +1 -1
- package/dist/resources/indexes/HierarchicalNavigableSmallWorld.d.ts +21 -1
- package/dist/resources/indexes/HierarchicalNavigableSmallWorld.js +114 -5
- package/dist/resources/indexes/HierarchicalNavigableSmallWorld.js.map +1 -1
- package/dist/resources/indexes/fullTextDerivedIndex.d.ts +11 -2
- package/dist/resources/indexes/fullTextDerivedIndex.js +187 -24
- package/dist/resources/indexes/fullTextDerivedIndex.js.map +1 -1
- package/dist/resources/indexes/fullTextNativeBinding.d.ts +99 -1
- package/dist/resources/indexes/fullTextNativeBinding.js +35 -1
- package/dist/resources/indexes/fullTextNativeBinding.js.map +1 -1
- package/dist/resources/indexes/fullTextQueryIndex.d.ts +58 -0
- package/dist/resources/indexes/fullTextQueryIndex.js +943 -0
- package/dist/resources/indexes/fullTextQueryIndex.js.map +1 -0
- package/dist/resources/indexes/fullTextQueryProtocol.d.ts +6 -0
- package/dist/resources/indexes/fullTextQueryProtocol.js +32 -0
- package/dist/resources/indexes/fullTextQueryProtocol.js.map +1 -0
- package/dist/resources/indexes/nativeFullTextDerivedIndexLifecycle.d.ts +4 -1
- package/dist/resources/indexes/nativeFullTextDerivedIndexLifecycle.js +30 -7
- package/dist/resources/indexes/nativeFullTextDerivedIndexLifecycle.js.map +1 -1
- package/dist/resources/search.d.ts +41 -0
- package/dist/resources/search.js +468 -26
- package/dist/resources/search.js.map +1 -1
- package/dist/resources/transactionBroadcast.js +101 -50
- package/dist/resources/transactionBroadcast.js.map +1 -1
- package/dist/resources/typeParity.d.ts +9 -0
- package/dist/resources/typeParity.js +3 -0
- package/dist/resources/typeParity.js.map +1 -0
- package/dist/server/REST.js.map +1 -1
- package/dist/server/itc/serverHandlers.js +48 -8
- package/dist/server/itc/serverHandlers.js.map +1 -1
- package/dist/server/serverHelpers/contentTypes.d.ts +7 -0
- package/dist/server/serverHelpers/contentTypes.js +36 -23
- package/dist/server/serverHelpers/contentTypes.js.map +1 -1
- package/dist/server/serverHelpers/uwsServer.js +28 -5
- package/dist/server/serverHelpers/uwsServer.js.map +1 -1
- package/dist/server/threads/itc.js +19 -0
- package/dist/server/threads/itc.js.map +1 -1
- package/dist/server/threads/manageThreads.d.ts +3 -0
- package/dist/server/threads/manageThreads.js +90 -23
- package/dist/server/threads/manageThreads.js.map +1 -1
- package/dist/utility/operation_authorization.js +25 -7
- package/dist/utility/operation_authorization.js.map +1 -1
- package/dist/utility/signalling.js +3 -0
- package/dist/utility/signalling.js.map +1 -1
- package/dist/validation/searchValidator.js +22 -2
- package/dist/validation/searchValidator.js.map +1 -1
- package/npm-shrinkwrap.json +22 -22
- package/package.json +2 -4
- package/resources/DESIGN.md +19 -2
- package/resources/DatabaseTransaction.ts +40 -10
- package/resources/FULL_TEXT.md +97 -0
- package/resources/ResourceInterface.ts +8 -2
- package/resources/Table.ts +767 -17
- package/resources/analytics/write.ts +3 -3
- package/resources/blob.ts +183 -1
- package/resources/databases.ts +126 -14
- package/resources/derivedIndexRuntime.ts +79 -6
- package/resources/derivedIndexes.ts +142 -18
- package/resources/fullTextSchema.ts +55 -9
- package/resources/fullTextSchemaLifecycle.ts +45 -0
- package/resources/graphql.ts +33 -12
- package/resources/indexes/DESIGN.md +167 -15
- package/resources/indexes/HierarchicalNavigableSmallWorld.ts +125 -5
- package/resources/indexes/fullTextDerivedIndex.ts +216 -32
- package/resources/indexes/fullTextNativeBinding.ts +133 -3
- package/resources/indexes/fullTextQueryIndex.ts +1120 -0
- package/resources/indexes/fullTextQueryProtocol.ts +35 -0
- package/resources/indexes/nativeFullTextDerivedIndexLifecycle.ts +28 -8
- package/resources/search.ts +513 -26
- package/resources/transactionBroadcast.ts +86 -47
- package/resources/typeParity.ts +20 -0
- package/schema.graphql +18 -5
- package/server/DESIGN.md +18 -0
- package/server/REST.ts +1 -1
- package/server/itc/serverHandlers.js +61 -8
- package/server/serverHelpers/contentTypes.ts +31 -26
- package/server/serverHelpers/uwsServer.ts +35 -7
- package/server/threads/itc.js +21 -0
- package/server/threads/manageThreads.js +95 -21
- package/studio/web/assets/{Chat-Bq60AO-g.js → Chat-CNEKMllJ.js} +13 -13
- package/studio/web/assets/FloatingChat-CEnWhHeR.js +23 -0
- package/studio/web/assets/SearchDialog-CwhJdEdY.js +1 -0
- package/studio/web/assets/{abnfDiagram-O67JEVCF-DpPZWMOf.js → abnfDiagram-O67JEVCF-raXE027J.js} +1 -1
- package/studio/web/assets/alertDialog-BbWD9Xxa.js +1 -0
- package/studio/web/assets/apiToken-DC5vsaaO.js +1 -0
- package/studio/web/assets/applications-DQZxDy0x.js +296 -0
- package/studio/web/assets/architecture-7GRP2DOG-DDmz3hpI.js +1 -0
- package/studio/web/assets/{architectureDiagram-NJMV4G6O-OmgLraID.js → architectureDiagram-NJMV4G6O-C8_Eet7w.js} +1 -1
- package/studio/web/assets/arrow-up-D2g_57vT.js +1 -0
- package/studio/web/assets/authStore-CJCaADw9.js +3 -0
- package/studio/web/assets/{blockDiagram-BEXU5L5S-Cv7CD3Ai.js → blockDiagram-BEXU5L5S-DR6S5-1D.js} +1 -1
- package/studio/web/assets/{button-MFP9n8Ck.js → button-DWwfgGWI.js} +2 -2
- package/studio/web/assets/{c4Diagram-YGBWAQC7-Ds1vKYnN.js → c4Diagram-YGBWAQC7-BAfZ1G01.js} +1 -1
- package/studio/web/assets/channel-D0AkPjJ8.js +1 -0
- package/studio/web/assets/{chevron-up-CFe8x5Bp.js → chevron-up-CFxbMGv2.js} +1 -1
- package/studio/web/assets/{chunk-3FUC2YCW-DpnBPH8B.js → chunk-3FUC2YCW-DuRp-j19.js} +1 -1
- package/studio/web/assets/{chunk-5DYCD2WN-LLLCQSzg.js → chunk-5DYCD2WN-BceBfIHT.js} +1 -1
- package/studio/web/assets/{chunk-742MDFTN-BL8J_pIO.js → chunk-742MDFTN-CIkt8ijt.js} +1 -1
- package/studio/web/assets/{chunk-7INBJB4K-C-hJHiiK.js → chunk-7INBJB4K-BpeAXyOi.js} +1 -1
- package/studio/web/assets/{chunk-7M6MHVWA-C6ZqkFbA.js → chunk-7M6MHVWA-h6WlaTzO.js} +1 -1
- package/studio/web/assets/{chunk-7PRAP22T-CUXM8BG6.js → chunk-7PRAP22T-KyqX6x3f.js} +1 -1
- package/studio/web/assets/{chunk-GTNCS2PH-DJ2BFvS1.js → chunk-GTNCS2PH-DGHLXyow.js} +1 -1
- package/studio/web/assets/{chunk-GWA4HPMP-C42bSqLL.js → chunk-GWA4HPMP-DsejB6gA.js} +1 -1
- package/studio/web/assets/{chunk-MBY4JIJT-C-5jz6Lo.js → chunk-MBY4JIJT-BwSGweQP.js} +1 -1
- package/studio/web/assets/{chunk-NETBCI7D-D-bzlN4r.js → chunk-NETBCI7D-C8UnOw_f.js} +1 -1
- package/studio/web/assets/{chunk-O7XYJQB3-ep14zA23.js → chunk-O7XYJQB3-CBS3nZYV.js} +1 -1
- package/studio/web/assets/{chunk-UA2S7LBM-BMRWRHED.js → chunk-UA2S7LBM-BHpEdH8D.js} +1 -1
- package/studio/web/assets/{chunk-WEXAMYUT-DdLYVdX1.js → chunk-WEXAMYUT-BAIhTwyh.js} +1 -1
- package/studio/web/assets/{chunk-Z7XXMR3K-BmKEDjcP.js → chunk-Z7XXMR3K-DSHbImdf.js} +1 -1
- package/studio/web/assets/{chunk-ZIGJFQKS-DLZgdNxA.js → chunk-ZIGJFQKS-Cs8fUn7y.js} +1 -1
- package/studio/web/assets/{classDiagram-v2-NBCMYWYE-DOI5vgKT.js → classDiagram-v2-NBCMYWYE-DmDPkt2u.js} +1 -1
- package/studio/web/assets/cn-6T8VranJ.js +1 -0
- package/studio/web/assets/{codicon-Brq4_Ui5.ttf → codicon-DF1abBS2.ttf} +0 -0
- package/studio/web/assets/{createLucideIcon-bziYxJli.js → createLucideIcon-fO5dL7D5.js} +1 -1
- package/studio/web/assets/{css.worker-l1psR_wG.js → css.worker-DoT-OOzx.js} +36 -36
- package/studio/web/assets/{cssMode-DJyc9mHj.js → cssMode-Dz2DxF4e.js} +1 -1
- package/studio/web/assets/{cynefin-OW5HDTMX-BSW-DB7S.js → cynefin-OW5HDTMX-DIvbXLSj.js} +1 -1
- package/studio/web/assets/{cynefinDiagram-VND7K2PF-CbAHEwxt.js → cynefinDiagram-VND7K2PF-BdtaXiCE.js} +1 -1
- package/studio/web/assets/{dagre-6A5THRUB-Csxewgs4.js → dagre-6A5THRUB-C7RUWHyz.js} +1 -1
- package/studio/web/assets/{diagram-22UHCM2B-a0rt8tY4.js → diagram-22UHCM2B-DJCvcaWP.js} +1 -1
- package/studio/web/assets/{diagram-3UASUU5V-lEj2SVNh.js → diagram-3UASUU5V-Du6VKWln.js} +1 -1
- package/studio/web/assets/{diagram-ATOU4E4O-HcrAeB-q.js → diagram-ATOU4E4O-dxRfeG4B.js} +1 -1
- package/studio/web/assets/{diagram-CDSNMT55-CYxiuyEx.js → diagram-CDSNMT55-l5Ga0u-W.js} +1 -1
- package/studio/web/assets/{diagram-MLGK6HIB-ChbCJTgJ.js → diagram-MLGK6HIB-CNQY47LA.js} +1 -1
- package/studio/web/assets/{diagram-MPIPVDR6-DaX9Y-N3.js → diagram-MPIPVDR6-7prveD1B.js} +1 -1
- package/studio/web/assets/{dist-Cz2Y6-6G.js → dist-BrRuMUzt.js} +1 -1
- package/studio/web/assets/{download-CtostjYn.js → download-Cn6tPG4L.js} +1 -1
- package/studio/web/assets/{ebnfDiagram-ZINNZB2B-CwAsENB1.js → ebnfDiagram-ZINNZB2B-5OnkTpfi.js} +1 -1
- package/studio/web/assets/{editor-DDIsQfj6.js → editor-B3mjd3tz.js} +1 -1
- package/studio/web/assets/editor.worker-Yd1NR0QT.js +33 -0
- package/studio/web/assets/{elk-276RUBZZ-Hexg9E4D.js → elk-276RUBZZ-DxDQPjQT.js} +1 -1
- package/studio/web/assets/{erDiagram-OPXOYQCR-CeC-wwO1.js → erDiagram-OPXOYQCR-CQlCX84D.js} +1 -1
- package/studio/web/assets/eventmodeling-NTZA5JFV-DONQf5aG.js +1 -0
- package/studio/web/assets/flowDiagram-KWPJA3E3-BoZ8_QR1.js +1 -0
- package/studio/web/assets/{ganttDiagram-FUAMR5RP-C2_Hhl1-.js → ganttDiagram-FUAMR5RP-bloX3Voy.js} +1 -1
- package/studio/web/assets/{getAnalytics-CjthMQqF.js → getAnalytics-C0FHjtdQ.js} +1 -1
- package/studio/web/assets/{gitGraph-4MIJSDKK-xIk5Vuew.js → gitGraph-4MIJSDKK-BrXWmJt2.js} +1 -1
- package/studio/web/assets/{gitGraphDiagram-X574FWY7-CMxJ34up.js → gitGraphDiagram-X574FWY7-C2KnnZeY.js} +1 -1
- package/studio/web/assets/{html-g1BJHxLy.js → html-BsRGM7tp.js} +1 -1
- package/studio/web/assets/{html.worker-DdwM4IrA.js → html.worker-B1dwY0Ay.js} +27 -27
- package/studio/web/assets/{htmlMode-Vh5IglCQ.js → htmlMode-tUKJaA3o.js} +1 -1
- package/studio/web/assets/index-72cEH474.css +1 -0
- package/studio/web/assets/index-BuqFjN5K.js +828 -0
- package/studio/web/assets/index.lazy-CcpfQFY_.js +2 -0
- package/studio/web/assets/{info-A6RAGUB7-o49TEbj-.js → info-A6RAGUB7-BhzBHe4U.js} +1 -1
- package/studio/web/assets/{infoDiagram-VRGFBTTK-Cjhh7yzZ.js → infoDiagram-VRGFBTTK-B0J1132p.js} +1 -1
- package/studio/web/assets/{ishikawaDiagram-OU5B5YK6-C63aXRmh.js → ishikawaDiagram-OU5B5YK6-BNk0tuQx.js} +1 -1
- package/studio/web/assets/{javascript-5VIolqf7.js → javascript-BZ_uRGHl.js} +1 -1
- package/studio/web/assets/{journeyDiagram-ZHPQQLJL-D_o0P5Mu.js → journeyDiagram-ZHPQQLJL-Be79RMQM.js} +1 -1
- package/studio/web/assets/json.worker-BQqXGI3X.js +65 -0
- package/studio/web/assets/{jsonMode-DG4zQraI.js → jsonMode-DgB-aFMj.js} +1 -1
- package/studio/web/assets/{kanban-definition-PNTS6WVX-CTI9SSxY.js → kanban-definition-PNTS6WVX-4uuoQP9W.js} +1 -1
- package/studio/web/assets/{languageServices-t5FBpX-i.js → languageServices-BleuBw17.js} +1 -1
- package/studio/web/assets/localStorageKeys-etzFY_xV.js +1 -0
- package/studio/web/assets/{lspLanguageFeatures-DgVnNPh-.js → lspLanguageFeatures-ByVYLSsQ.js} +1 -1
- package/studio/web/assets/{mermaid-parser.core-C2lcH_jP.js → mermaid-parser.core-V1UYMeXV.js} +3 -3
- package/studio/web/assets/{mermaid.core-BG5bjfIG.js → mermaid.core-1PX6R49U.js} +4 -4
- package/studio/web/assets/{mindmap-definition-NLK3R4M7-BK7ho6JD.js → mindmap-definition-NLK3R4M7-C70Ctm0J.js} +1 -1
- package/studio/web/assets/notifications-D1Nf-Edn.js +1 -0
- package/studio/web/assets/notifications-TuzN0IMi.js +1 -0
- package/studio/web/assets/{packet-AYTQ26CC-BHPy6KBV.js → packet-AYTQ26CC-DWbXHx5g.js} +1 -1
- package/studio/web/assets/{pegDiagram-GJSIUBJH-DzjwG3jO.js → pegDiagram-GJSIUBJH-d6z1cc7W.js} +1 -1
- package/studio/web/assets/{pie-WAS4IAKB-D0cP-DTU.js → pie-WAS4IAKB-DjoTm3yr.js} +1 -1
- package/studio/web/assets/{pieDiagram-5QR66LMP-C_hOP9Lv.js → pieDiagram-5QR66LMP-Dz3RHpDA.js} +1 -1
- package/studio/web/assets/profile-D7C7Lupz.js +1 -0
- package/studio/web/assets/{quadrantDiagram-O4NWA36T-CZW8PbV1.js → quadrantDiagram-O4NWA36T-CIF5Qkou.js} +1 -1
- package/studio/web/assets/{radar-RG4KPBEZ-C8fk0U81.js → radar-RG4KPBEZ-CVBgjCOj.js} +1 -1
- package/studio/web/assets/{railroad-74A4TZTK-B60FWm17.js → railroad-74A4TZTK-BdhXw10Q.js} +1 -1
- package/studio/web/assets/railroad-abnf-HS5TGJTU-BcoQPN39.js +1 -0
- package/studio/web/assets/railroad-ebnf-LZEXJU2U-B1Lke2JN.js +1 -0
- package/studio/web/assets/railroad-peg-WCYAUIDC-D-oJFkiV.js +1 -0
- package/studio/web/assets/{railroadDiagram-XR7U4H2S-DyyNmgbU.js → railroadDiagram-XR7U4H2S-y3Jv2U6N.js} +1 -1
- package/studio/web/assets/regions-nGQCm-v8.js +1 -0
- package/studio/web/assets/register-C3x9Ze6P.js +909 -0
- package/studio/web/assets/register-DjpsE8to.css +1 -0
- package/studio/web/assets/{requirementDiagram-PLB6GJNP-C4H-mLKu.js → requirementDiagram-PLB6GJNP-ChINZEmu.js} +1 -1
- package/studio/web/assets/restartGate-nHAFLDh-.js +1 -0
- package/studio/web/assets/{sankeyDiagram-IPEJSGJF-DHDyga4c.js → sankeyDiagram-IPEJSGJF-D9AyLce2.js} +1 -1
- package/studio/web/assets/{sequenceDiagram-PO4LG4MO-CdNcHXRI.js → sequenceDiagram-PO4LG4MO-hfk4aIEm.js} +1 -1
- package/studio/web/assets/{setComponentFile-z4-qJCbA.js → setComponentFile-DpjrRqeG.js} +1 -1
- package/studio/web/assets/{setup-CS50uJ4x.js → setup-CGiKLZ9e.js} +2 -2
- package/studio/web/assets/{stateDiagram-v2-GCMORJYK-CpKuXq15.js → stateDiagram-v2-GCMORJYK-Bs8RIb9K.js} +1 -1
- package/studio/web/assets/status-CGJwrNPH.js +61 -0
- package/studio/web/assets/{swimlanes-2SLR337P-NRHa2D8I.js → swimlanes-2SLR337P-B52xrf6t.js} +1 -1
- package/studio/web/assets/swimlanesDiagram-TC7HE7FX-DfN7BAP5.js +8 -0
- package/studio/web/assets/tabs-BCTQzoQK.js +1 -0
- package/studio/web/assets/{timeline-definition-EJHVYXUP-D39Y-xpq.js → timeline-definition-EJHVYXUP-DJSITpQX.js} +1 -1
- package/studio/web/assets/toggleHighContrast-C-Ne6P7T.js +51 -0
- package/studio/web/assets/toggleHighContrast-n7clI0si.css +1 -0
- package/studio/web/assets/{treeView-Q6P3EWNA-CRWR9OqD.js → treeView-Q6P3EWNA-C-epdxRo.js} +1 -1
- package/studio/web/assets/{treemap-WGGIJYW6-ZfXyJgLO.js → treemap-WGGIJYW6-DOVyp_zw.js} +1 -1
- package/studio/web/assets/{ts.worker-DDMUrpqh.js → ts.worker-Clm12uY6.js} +131 -131
- package/studio/web/assets/{tsMode-D83Gg3cq.js → tsMode-CSreYTys.js} +1 -1
- package/studio/web/assets/{typescript-Dfp-GrNq.js → typescript-MnxBk89W.js} +1 -1
- package/studio/web/assets/{useEntityRestURL-BKetlINf.js → useEntityRestURL-TNrh77-s.js} +1 -1
- package/studio/web/assets/{usecaseDiagram-POWQR4AR-jf-zIDZ_.js → usecaseDiagram-POWQR4AR-B6252cbq.js} +1 -1
- package/studio/web/assets/{vendor-core-BdkdbUek.js → vendor-core-6LhfJlRC.js} +1 -1
- package/studio/web/assets/vendor-datadog-ozKf-pts.js +6 -0
- package/studio/web/assets/{vendor-react-izWchTRL.js → vendor-react-BOliS0BL.js} +1 -1
- package/studio/web/assets/vendor-tanstack-Bntfka-M.js +2 -0
- package/studio/web/assets/vendor-ui-9EYYDgoN.js +41 -0
- package/studio/web/assets/{vennDiagram-UO4OBE2U-BKYAZUh_.js → vennDiagram-UO4OBE2U-DZTsDA4k.js} +1 -1
- package/studio/web/assets/{wardley-WFR3VGLG-BO7f36ci.js → wardley-WFR3VGLG-Cfqoe6z4.js} +1 -1
- package/studio/web/assets/{wardleyDiagram-VNRHLVJA-Djb0JDQc.js → wardleyDiagram-VNRHLVJA-qu_f7Tvl.js} +1 -1
- package/studio/web/assets/{workers-CCvmDLH3.js → workers-BfZlUNVI.js} +3 -3
- package/studio/web/assets/{xml-06pZp00U.js → xml-BHd6qGH7.js} +1 -1
- package/studio/web/assets/{xychartDiagram-PMCCYNJV-BQh9ubvb.js → xychartDiagram-PMCCYNJV-UrOKRsmR.js} +1 -1
- package/studio/web/assets/{yaml-iSnWMVlV.js → yaml-C53R8OQq.js} +1 -1
- package/studio/web/assets/{yaml.worker-DAK2o_H1.js → yaml.worker-D9JekGdp.js} +183 -183
- package/studio/web/index.html +15 -15
- package/utility/operation_authorization.ts +27 -7
- package/utility/signalling.ts +3 -0
- package/validation/searchValidator.ts +22 -13
- package/studio/web/assets/FloatingChat-D4560T0p.js +0 -23
- package/studio/web/assets/alertDialog-UrbiMgQt.js +0 -1
- package/studio/web/assets/apiToken-BEvbFPMc.js +0 -1
- package/studio/web/assets/applications-CSUVTH4E.js +0 -296
- package/studio/web/assets/architecture-7GRP2DOG-11l4Ny1l.js +0 -1
- package/studio/web/assets/authStore-5jSS5pTW.js +0 -3
- package/studio/web/assets/channel-BDtouPd6.js +0 -1
- package/studio/web/assets/dialog-DTepachC.js +0 -1
- package/studio/web/assets/editor.worker-CkkvdXvO.js +0 -33
- package/studio/web/assets/eventmodeling-NTZA5JFV-CFb03e0o.js +0 -1
- package/studio/web/assets/flowDiagram-KWPJA3E3-Cidi00_Y.js +0 -1
- package/studio/web/assets/index-CPoDzQTe.css +0 -1
- package/studio/web/assets/index-DGRAzvO4.js +0 -824
- package/studio/web/assets/index.lazy-vgSic7et.js +0 -2
- package/studio/web/assets/json.worker-CSi2hF-a.js +0 -65
- package/studio/web/assets/notifications-BeWcX04m.js +0 -1
- package/studio/web/assets/notifications-DUGWrWoO.js +0 -1
- package/studio/web/assets/profile-BvUEDBzW.js +0 -1
- package/studio/web/assets/railroad-abnf-HS5TGJTU-CLMVZvH0.js +0 -1
- package/studio/web/assets/railroad-ebnf-LZEXJU2U--tlwFoVB.js +0 -1
- package/studio/web/assets/railroad-peg-WCYAUIDC-Dpuslblm.js +0 -1
- package/studio/web/assets/regions-CB-_2m7M.js +0 -1
- package/studio/web/assets/register-1w1Zr3b2.js +0 -848
- package/studio/web/assets/register-jjEx9u7D.css +0 -1
- package/studio/web/assets/setLocalStorage-D_kflv4U.js +0 -1
- package/studio/web/assets/status-CI0wOdOE.js +0 -61
- package/studio/web/assets/swimlanesDiagram-TC7HE7FX-BBk4t4hO.js +0 -8
- package/studio/web/assets/tabs-CnQ_zenf.js +0 -1
- package/studio/web/assets/toggleHighContrast--ixms01u.css +0 -1
- package/studio/web/assets/toggleHighContrast-BsAE4jPe.js +0 -49
- package/studio/web/assets/useLocalStorage-BYoZF60G.js +0 -1
- package/studio/web/assets/vendor-datadog-fCQhBHGk.js +0 -6
- package/studio/web/assets/vendor-tanstack-CQ1aRjVa.js +0 -2
- package/studio/web/assets/vendor-ui-aE1tHcW7.js +0 -41
- package/studio/web/assets/x-vYntCxm4.js +0 -1
|
@@ -2,7 +2,9 @@ import { ClientError } from '../utility/errors/hdbError.ts';
|
|
|
2
2
|
import {
|
|
3
3
|
compileFullTextDefinition,
|
|
4
4
|
compileFullTextDefinitions,
|
|
5
|
+
compileFullTextFields,
|
|
5
6
|
compileValidFullTextDefinitions,
|
|
7
|
+
persistedFullTextIndexNames,
|
|
6
8
|
sortFullTextDefinitions,
|
|
7
9
|
type FullTextDefinition,
|
|
8
10
|
type FullTextSchemaAttribute,
|
|
@@ -22,6 +24,49 @@ export function definitionsEqual(left: unknown, right: unknown): boolean {
|
|
|
22
24
|
return serializeFullTextState(left ?? []) === serializeFullTextState(right ?? []);
|
|
23
25
|
}
|
|
24
26
|
|
|
27
|
+
export function readPersistedFullTextFields(
|
|
28
|
+
values: unknown,
|
|
29
|
+
definitions: unknown,
|
|
30
|
+
attributes: readonly FullTextSchemaAttribute[],
|
|
31
|
+
warn: FullTextWarning
|
|
32
|
+
): string[] {
|
|
33
|
+
const persistedNames = persistedFullTextIndexNames(definitions);
|
|
34
|
+
if (values === undefined) return persistedNames;
|
|
35
|
+
const names = persistedNames.map((name) => ({ name }));
|
|
36
|
+
try {
|
|
37
|
+
return compileFullTextFields(values, names, attributes, true);
|
|
38
|
+
} catch (error) {
|
|
39
|
+
if (!(error instanceof ClientError)) throw error;
|
|
40
|
+
warn(`Invalid persisted full-text fields; reserving every persisted index name: ${error.message}`);
|
|
41
|
+
return persistedNames;
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
export function mergePeerFullTextFields(
|
|
46
|
+
persistedValues: unknown,
|
|
47
|
+
incomingValues: unknown,
|
|
48
|
+
persistedDefinitions: unknown,
|
|
49
|
+
finalDefinitions: unknown,
|
|
50
|
+
attributes: readonly FullTextSchemaAttribute[],
|
|
51
|
+
warn: FullTextWarning
|
|
52
|
+
): string[] {
|
|
53
|
+
const retained = readPersistedFullTextFields(persistedValues, persistedDefinitions, attributes, warn);
|
|
54
|
+
const finalNames = persistedFullTextIndexNames(finalDefinitions);
|
|
55
|
+
if (incomingValues === undefined) incomingValues = finalNames;
|
|
56
|
+
try {
|
|
57
|
+
compileFullTextFields(
|
|
58
|
+
incomingValues,
|
|
59
|
+
finalNames.map((name) => ({ name })),
|
|
60
|
+
attributes
|
|
61
|
+
);
|
|
62
|
+
} catch (error) {
|
|
63
|
+
if (!(error instanceof ClientError)) throw error;
|
|
64
|
+
warn(`Ignoring invalid peer full-text fields: ${error.message}`);
|
|
65
|
+
}
|
|
66
|
+
const existingNames = new Set(persistedFullTextIndexNames(persistedDefinitions));
|
|
67
|
+
return [...new Set([...retained, ...finalNames.filter((name) => !existingNames.has(name))])].sort();
|
|
68
|
+
}
|
|
69
|
+
|
|
25
70
|
export function readPersistedFullTextDefinitions(
|
|
26
71
|
values: unknown,
|
|
27
72
|
attributes: readonly FullTextSchemaAttribute[],
|
package/resources/graphql.ts
CHANGED
|
@@ -8,7 +8,7 @@ import type { DirectiveNode, NamedTypeNode, StringValueNode, ValueNode } from 'g
|
|
|
8
8
|
import { ClientError } from '../utility/errors/hdbError.ts';
|
|
9
9
|
import { attributeToFragment, type JsonSchemaFragment } from './jsonSchemaTypes.ts';
|
|
10
10
|
import harperLogger from '../utility/logging/harper_logger.ts';
|
|
11
|
-
import { compileFullTextDefinitions } from './fullTextSchema.ts';
|
|
11
|
+
import { compileFullTextDefinitions, compileFullTextFields } from './fullTextSchema.ts';
|
|
12
12
|
import { validateDecisionSchema } from './models/decision.ts';
|
|
13
13
|
import { assertDerivedFieldOwnership } from './models/embedHook.ts';
|
|
14
14
|
import type { DecideConfig } from './models/decideHook.ts';
|
|
@@ -253,6 +253,7 @@ async function processGraphQLSchema(
|
|
|
253
253
|
attributes,
|
|
254
254
|
properties: typeProperties,
|
|
255
255
|
fullTextIndexes: [],
|
|
256
|
+
fullTextFields: [],
|
|
256
257
|
};
|
|
257
258
|
if (definition.description?.value) typeDef.description = definition.description.value;
|
|
258
259
|
types.set(typeName, typeDef);
|
|
@@ -287,15 +288,8 @@ async function processGraphQLSchema(
|
|
|
287
288
|
if (directive.name.value === 'splitSegments') typeDef.splitSegments = true;
|
|
288
289
|
if (directive.name.value === 'replicate') typeDef.replicate = true;
|
|
289
290
|
if (directive.name.value === 'hidden') typeDef.hidden = true;
|
|
290
|
-
if (directive.name.value === 'fullText')
|
|
291
|
-
|
|
292
|
-
for (const arg of directive.arguments || []) {
|
|
293
|
-
if (Object.hasOwn(definition, arg.name.value))
|
|
294
|
-
throw new ClientError(`@fullText declares "${arg.name.value}" more than once`, 400);
|
|
295
|
-
definition[arg.name.value] = coerceDirectiveValue(arg.value);
|
|
296
|
-
}
|
|
297
|
-
typeDef.fullTextIndexes.push(definition);
|
|
298
|
-
}
|
|
291
|
+
if (directive.name.value === 'fullText')
|
|
292
|
+
throw new ClientError('@fullText must be declared on a FullText field', 400);
|
|
299
293
|
if (directive.name.value === 'export') {
|
|
300
294
|
typeDef.export = true;
|
|
301
295
|
for (const arg of directive.arguments) {
|
|
@@ -318,12 +312,40 @@ async function processGraphQLSchema(
|
|
|
318
312
|
};
|
|
319
313
|
}
|
|
320
314
|
const typeName = (type as NamedTypeNode).name?.value;
|
|
315
|
+
if (typeName === 'FullText') throw new ClientError('FullText fields require @fullText', 400);
|
|
321
316
|
const property = { type: typeName };
|
|
322
317
|
Object.defineProperty(property, 'location', { value: type.loc.startToken });
|
|
323
318
|
return property;
|
|
324
319
|
}
|
|
325
320
|
const attributesObject = {};
|
|
326
321
|
for (const field of definition.fields) {
|
|
322
|
+
const name = field.name.value;
|
|
323
|
+
const fullTextDirectives = field.directives.filter(({ name }) => name.value === 'fullText');
|
|
324
|
+
if (fullTextDirectives.length > 0) {
|
|
325
|
+
if (fullTextDirectives.length !== 1)
|
|
326
|
+
throw new ClientError(`Full-text field "${name}" requires exactly one @fullText directive`, 400);
|
|
327
|
+
if (field.type.kind !== 'NamedType' || field.type.name.value !== 'FullText')
|
|
328
|
+
throw new ClientError(`@fullText field "${name}" requires the nullable FullText type`, 400);
|
|
329
|
+
for (const directive of field.directives) {
|
|
330
|
+
if (directive.name.value !== 'fullText')
|
|
331
|
+
throw new ClientError(
|
|
332
|
+
`Full-text field "${name}" cannot use @${directive.name.value}; configure permissions and record values on its source fields`,
|
|
333
|
+
400
|
|
334
|
+
);
|
|
335
|
+
}
|
|
336
|
+
const index: Record<string, unknown> = Object.create(null);
|
|
337
|
+
index.name = name;
|
|
338
|
+
for (const arg of fullTextDirectives[0].arguments || []) {
|
|
339
|
+
if (arg.name.value === 'name')
|
|
340
|
+
throw new ClientError(`@fullText uses the field name "${name}"; remove its name argument`, 400);
|
|
341
|
+
if (Object.hasOwn(index, arg.name.value))
|
|
342
|
+
throw new ClientError(`@fullText declares "${arg.name.value}" more than once`, 400);
|
|
343
|
+
index[arg.name.value] = coerceDirectiveValue(arg.value);
|
|
344
|
+
}
|
|
345
|
+
typeDef.fullTextIndexes.push(index);
|
|
346
|
+
typeDef.fullTextFields.push(name);
|
|
347
|
+
continue;
|
|
348
|
+
}
|
|
327
349
|
const property = getProperty(field.type);
|
|
328
350
|
property.name = field.name.value;
|
|
329
351
|
if (field.description?.value) property.description = field.description.value;
|
|
@@ -392,8 +414,6 @@ async function processGraphQLSchema(
|
|
|
392
414
|
// Resolved after the field's other directives so @primaryKey / @computed in any
|
|
393
415
|
// order are seen; the ownership checks below need every field of the type.
|
|
394
416
|
property.decide = directive;
|
|
395
|
-
} else if (directiveName === 'fullText') {
|
|
396
|
-
throw new ClientError('@fullText must be declared on a @table type, not on a field', 400);
|
|
397
417
|
} else if (directiveName === 'relationship') {
|
|
398
418
|
const relationshipDefinition = {};
|
|
399
419
|
for (const arg of directive.arguments) {
|
|
@@ -447,6 +467,7 @@ async function processGraphQLSchema(
|
|
|
447
467
|
if (typeDef.fullTextIndexes.length > 0 && !typeDef.table)
|
|
448
468
|
throw new ClientError('@fullText is only supported on a @table type', 400);
|
|
449
469
|
typeDef.fullTextIndexes = compileFullTextDefinitions(typeDef.fullTextIndexes, attributes);
|
|
470
|
+
typeDef.fullTextFields = compileFullTextFields(typeDef.fullTextFields, typeDef.fullTextIndexes, attributes);
|
|
450
471
|
// Project the array form into the canonical `properties` Record (JSON-Schema-shaped,
|
|
451
472
|
// keyed by attribute name). Both shapes are co-populated in this single pass;
|
|
452
473
|
// downstream consumers (MCP, OpenAPI) read whichever form they prefer.
|
|
@@ -178,11 +178,38 @@ unit switch does not shift the estimate on its own. The ef term remains the conf
|
|
|
178
178
|
not the runtime auto-scaled value, so the planner increasingly underestimates vector traversal cost
|
|
179
179
|
as an automatically scaled graph grows.
|
|
180
180
|
|
|
181
|
+
## A filtered vector search may admit from an index key set only if the set holds every predicate match
|
|
182
|
+
|
|
183
|
+
`search.ts → planCandidateKeys` turns companion AND conditions a secondary index answers (equality,
|
|
184
|
+
`gt`/`ge`/`lt`/`le`, the `between` family, nested AND/OR over those) into key-only range scans, and the
|
|
185
|
+
HNSW index admits from the keys instead of loading a record per visited node (#2688). A set covering
|
|
186
|
+
every pushed-down condition, with no `rowFilter`/`vectorFilter`, replaces the predicate; otherwise it
|
|
187
|
+
gates the residual predicate. The post-filter re-checks every row, so the set may over-admit but must
|
|
188
|
+
never omit: an omission is unrecoverable and makes results depend on whether the cost model built it.
|
|
189
|
+
|
|
190
|
+
`planCandidateKeyScan` enforces that by running the condition's own `filterByType` predicate on the
|
|
191
|
+
records an index cannot supply: one lacking the attribute, a null one when the index holds no nulls,
|
|
192
|
+
and a `0`, because a number written as `-0` is indexed under an encoding that sorts past every number
|
|
193
|
+
and decodes to a wrong primary key (ordered-binary; index-led queries miss that row too). A condition
|
|
194
|
+
admitting any of them stays on the predicate path. `compareKeys` ranks undefined below every scalar, so
|
|
195
|
+
this declines `lt`/`le` over a number, string or boolean bound, `ge null` and any range covering 0; the
|
|
196
|
+
`lt`/`le` that remain (a BigInt bound) start at `null`, not `searchByIndex`'s `true`, so indexed nulls
|
|
197
|
+
are read. Normalizing `-0` in the encoder or index writer would let zero-covering ranges plan again.
|
|
198
|
+
|
|
199
|
+
Building the set is a cost decision: one index-entry read per match against a record load per visited
|
|
200
|
+
node, where filling `ef` matches at selectivity `s` takes about `ef / s` visits. `KEYS_PER_PREDICATE_VISIT`
|
|
201
|
+
is that ratio at the warm, regression-safe end of the measured range; `MAX_CANDIDATE_KEYS` and half the
|
|
202
|
+
node count cap the synchronous scan. A complete set derives the visit budget from its selectivity,
|
|
203
|
+
widening the `filterExpansion` budget by at most `ALLOW_SET_BUDGET_MAX_WIDENING` and never narrowing it;
|
|
204
|
+
a configured `filterExpansion` stays authoritative. `CandidateKeySet` is the one identity rule for both
|
|
205
|
+
the scan's intersection and admission. A node-id bitset for the native plane is not built: the
|
|
206
|
+
pk → node-id lookup costs more than the record load it saves.
|
|
207
|
+
|
|
181
208
|
## Derived-index runtime: committed-log delivery to native index backends (`resources/derivedIndexRuntime.ts`)
|
|
182
209
|
|
|
183
|
-
A derived index (the native HNSW
|
|
184
|
-
that lives outside the record transaction: its apply is native
|
|
185
|
-
|
|
210
|
+
A derived index (the native HNSW or Tantivy full-text plane) is a materialized view
|
|
211
|
+
that lives outside the record transaction: its apply is native and its durability barrier is an
|
|
212
|
+
`msync` or a segment publish, neither of which belongs on the commit path.
|
|
186
213
|
The runtime is the one Harper-side implementation of the delivery protocol in harper#2489: **the
|
|
187
214
|
transaction log is the durable fact, a commit only wakes a runner, and every backend resumes from an
|
|
188
215
|
exact cursor into that log.** There is deliberately no second delivery fact — no transactional
|
|
@@ -327,16 +354,29 @@ cursor is backend-owned and validation is Harper's.
|
|
|
327
354
|
`@harperfast/fulltext/native`; it does not implement another replay or ownership protocol. Harper
|
|
328
355
|
turns each resolved mutation into one stable document id from `tableId` and the record id's
|
|
329
356
|
ordered-binary storage-key bytes, and
|
|
330
|
-
passes only the schema-selected string and array fields to the wrapper. Harper
|
|
331
|
-
|
|
332
|
-
|
|
357
|
+
passes only the schema-selected string and array fields to the wrapper. Harper validates each selected
|
|
358
|
+
value, including every array element, and resolves text Blobs before submission. The wrapper owns
|
|
359
|
+
native document validation, Tantivy schema, exact frame partitioning, its exclusive writer, segment
|
|
360
|
+
publication, and file lifecycle. Harper keeps accepted runtime batches in a 64 MiB bounded queue and submits at most 256
|
|
333
361
|
records or 5 ms of conversion work per turn, so the runtime's 4096-record chunk cannot become one
|
|
334
|
-
long event-loop task.
|
|
362
|
+
long event-loop task. Awaited Blob reads yield the event loop and therefore do not consume that
|
|
363
|
+
conversion budget; otherwise ordinary storage latency would fragment a rebuild into small native
|
|
364
|
+
mutation submissions. Blob-bearing slices also have a wall-time ceiling derived from the writer shutdown
|
|
365
|
+
budget, so a run of slow reads cannot keep one drain turn alive through hundreds of per-record read
|
|
366
|
+
timeouts. A rebuild chunk without a source-size estimate consumes the adapter's entire
|
|
335
367
|
queue-byte allowance, ensuring that only one unknown-size chunk is retained at a time. Wrapper
|
|
336
368
|
rejections remove the previous document and count it as unindexable; they do not leave stale search
|
|
337
369
|
content. A projector returning null or no string-valued fields deletes the prior document rather
|
|
338
370
|
than indexing an empty replacement.
|
|
339
371
|
|
|
372
|
+
The elected runner opens one writer per index with two Tantivy indexing threads. The wrapper passes
|
|
373
|
+
the 64 MiB total writer budget to Tantivy's multi-threaded writer, which divides it into a 32 MiB
|
|
374
|
+
arena per indexing thread. Harper does not search through that writer handle, so it keeps the one
|
|
375
|
+
search worker required by the wrapper. Query readers can open on every Harper worker, so each uses
|
|
376
|
+
only the wrapper's minimum search pool for ordinary-versus-expensive query isolation rather than
|
|
377
|
+
scaling with host CPU. Read-only handles reserve search threads and queue bytes, but the wrapper
|
|
378
|
+
does not reserve their validation-only indexing-thread or writer-memory values.
|
|
379
|
+
|
|
340
380
|
Every runtime flush is a native publication barrier. Full-text activation chooses and benchmarks the
|
|
341
381
|
runtime flush thresholds; the adapter does not reinterpret a flush because the runtime uses durable
|
|
342
382
|
cursor progress to bound replay work and transaction-log retention.
|
|
@@ -356,7 +396,7 @@ not prove the index files should be reset. Persistent writer unavailability emit
|
|
|
356
396
|
or record content. Ownership handoff does not finish until drain and close prove quiescence. A
|
|
357
397
|
writer-open failure that lands after shutdown begins discards queued work before finalization so it
|
|
358
398
|
cannot enter another retry drain. Harper
|
|
359
|
-
gives native close its own 35-second bound and bounds the complete handoff at
|
|
399
|
+
gives native close its own 35-second bound and bounds the complete handoff at 105 seconds. If that proof fails, shutdown rejects and the runtime keeps its
|
|
360
400
|
runner lock, preventing a second writer. The underlying native operation continues and a later operator
|
|
361
401
|
retry attaches to the same shutdown rather than starting a competing close. A cursor that cannot fit
|
|
362
402
|
the native commit-payload limit is terminal for that backend instance: accepted work is rollback-closed
|
|
@@ -373,6 +413,8 @@ Inspection is synchronous and writer-free. The first durable-cursor read in each
|
|
|
373
413
|
refreshes native state, while later reads in the same acquisition use the cache; every shutdown path
|
|
374
414
|
invalidates it, including an owner that received no batch. A refresh failure never falls back to a
|
|
375
415
|
cached checkpoint because the runtime can publish `ready` before lazy writer-open reconciliation.
|
|
416
|
+
Query readers never use inspection: their committed payload belongs to the native snapshot they
|
|
417
|
+
actually search, while inspection is an ownership-acquisition and recovery primitive.
|
|
376
418
|
The synchronous backend contract has no retryable acquisition result, so an inspection exception
|
|
377
419
|
deliberately fails closed: the runtime condemns the generation and rebuilds from authoritative
|
|
378
420
|
records rather than trusting an unverified cursor. A transient filesystem error can therefore cost
|
|
@@ -381,7 +423,10 @@ Missing, cursorless, incompatible, or malformed native state has no usable curso
|
|
|
381
423
|
the runtime's ordinary local rebuild from records.
|
|
382
424
|
Reset first asks the wrapper to retire the live generation atomically, then reclaims only wrapper-
|
|
383
425
|
validated retired paths. The reset operation is tracked and bounded; shutdown attaches to the same
|
|
384
|
-
operation and keeps the runner lock if it cannot prove settlement.
|
|
426
|
+
operation and keeps the runner lock if it cannot prove settlement. If that operation outlives its
|
|
427
|
+
owner's bound, a successor waits for the same operation to settle and then issues its own epoch's
|
|
428
|
+
reset; it does not spend the remaining rebuild attempts on immediate owner-epoch mismatches.
|
|
429
|
+
Retired-path reclamation is
|
|
385
430
|
best-effort, serialized per lifecycle, and never extends that reset handoff. The native directory is node-local derived state: restarts reuse it and
|
|
386
431
|
replay after its payload; replicas independently derive it from their own applied transaction log;
|
|
387
432
|
backup and restore need only authoritative records and schema. A new or unusable directory serves no
|
|
@@ -396,16 +441,120 @@ the catalog first would turn a crash in that interval into an untraceable native
|
|
|
396
441
|
|
|
397
442
|
Tantivy files are not an opaque encrypted cache. They contain the document-id term dictionary,
|
|
398
443
|
analyzed term dictionaries, postings, frequencies and optionally positions; `surfaceTerms: true`
|
|
399
|
-
|
|
444
|
+
adds an unstemmed companion term field for surface-term features, but does not store the original
|
|
445
|
+
source values. Operators must protect
|
|
400
446
|
the full-text directory with the same filesystem controls as Harper data. Removing source records
|
|
401
447
|
does not erase old segment bytes immediately; normal Tantivy merge/reclamation governs physical
|
|
402
448
|
removal, and destroying an index uses the wrapper's retirement protocol.
|
|
403
449
|
|
|
404
|
-
The binding remains unloaded until a full-text declaration is activated.
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
450
|
+
The binding remains unloaded until a full-text declaration is activated. Harper exact-pins the
|
|
451
|
+
Fulltext package, documents it in `dependencies.md`, and loads its native prebuild in dedicated
|
|
452
|
+
Linux, macOS, and Windows CI jobs. A missing or incompatible binding is an activation error; Harper
|
|
453
|
+
does not silently omit the declared index.
|
|
454
|
+
|
|
455
|
+
### Full-text query plane
|
|
456
|
+
|
|
457
|
+
Each declared full-text index can lazily open a read-only native handle beside its derived-index writer. Readers
|
|
458
|
+
are keyed by native path, readiness id and owner epoch. A process-wide atomic publication generation
|
|
459
|
+
invalidates stale handles and notifies peer workers. A warm reader reloads in the background after a
|
|
460
|
+
notification, while every query still samples the generation and reloads on mismatch; notification
|
|
461
|
+
delivery is an optimization, not a correctness dependency. Workers that never query an index open no
|
|
462
|
+
reader or publication subscription. The shared derived-index runtime allocates this publication state
|
|
463
|
+
only for backends with external reader snapshots; HNSW does not pay for it. A waiting query samples the
|
|
464
|
+
generation after its coverage wait, so the data-publication bump that precedes the covering watermark
|
|
465
|
+
is included in reader acquisition. The wrapper refreshes the reader's committed payload with the
|
|
466
|
+
searcher, and Harper keeps that decoded checkpoint on the reader slot so coverage always describes the
|
|
467
|
+
snapshot being searched.
|
|
468
|
+
Coverage-only publications do not change the document set: the runtime permits them only after every
|
|
469
|
+
accepted index mutation through that capture is durable. The shared live watermark may therefore wake a
|
|
470
|
+
waiting query before Tantivy commits the newer restart checkpoint; the existing reader is data-current,
|
|
471
|
+
and the later publication notification reloads its checkpoint. Catalog-scale qualification measures this
|
|
472
|
+
metadata-only reload cadence so unrelated write traffic cannot become a hidden query-plane cost.
|
|
473
|
+
Missing, cursorless or malformed payloads fail closed and request one rebuild. A strict, non-waiting query on a
|
|
474
|
+
cold worker can return retryable lag while it opens the reader; retrying, or using
|
|
475
|
+
`waitForIndexMilliseconds`, allows the aligned reader to finish opening without synchronous filesystem
|
|
476
|
+
inspection. An epoch or query-configuration change requires a replacement handle. A publication reload
|
|
477
|
+
uses the existing handle only while it has no active lease; otherwise Harper opens a replacement and
|
|
478
|
+
retires the old handle after its search finishes. One logical query therefore uses one native snapshot
|
|
479
|
+
across all result pages and highlight tracing. The wrapper's reported query budget bounds that whole
|
|
480
|
+
query, including page fetches, source Blob reads and tracing, rather than restarting for each native
|
|
481
|
+
call. Highlight source materialization is bounded by both the wrapper's trace-record count and source-byte
|
|
482
|
+
limit before Blob reads start. Query leaves with the same field set reuse that materialization; distinct
|
|
483
|
+
field sets remain independently bounded. Requesting highlights from an index that did not enable them is rejected.
|
|
484
|
+
A retired handle accepts no new leases but remains open until its active searches finish.
|
|
485
|
+
Rejected closes remain tracked and are retried during the next pause or close; a fulfilled native
|
|
486
|
+
cleanup warning is logged. Neither replaces an otherwise successful query response.
|
|
487
|
+
One failed reload leaves the last aligned snapshot installed and returns retryable lag. Three
|
|
488
|
+
consecutive reload failures retire that handle so the next query reopens from native storage.
|
|
489
|
+
Table close, drop and schema replacement never force-close an active native handle. They stop waiting
|
|
490
|
+
after the wrapper's query budget plus a small settlement grace and report a retryable teardown failure;
|
|
491
|
+
the retired handle remains registered for reset fencing and closes when the query eventually releases
|
|
492
|
+
its lease. It unregisters only after that drain settles.
|
|
493
|
+
|
|
494
|
+
Reset is destructive, so the elected writer first pauses readers in every worker and waits for their
|
|
495
|
+
active leases to drain. Harper does not force-close a handle that native code may still be using; a
|
|
496
|
+
stuck native request therefore delays reset rather than risking use-after-close. The reset handoff
|
|
497
|
+
allows two reader-coordination bounds plus the native reset bound. Resume carries the
|
|
498
|
+
exact readiness-id and owner-epoch token that paused the path, preventing an old reset from reopening
|
|
499
|
+
readers after a newer owner has paused them. Epochs from different readiness generations are never
|
|
500
|
+
compared because each generation owns an independent counter.
|
|
501
|
+
The runtime publishes `rebuilding` before invoking reset, so a worker created after the pause
|
|
502
|
+
broadcast cannot admit a new reader. A worker that misses resume clears a token after that
|
|
503
|
+
token's own shared readiness reaches ready at the same or a newer owner epoch. A predecessor generation
|
|
504
|
+
that crashes during reset may never publish ready again. A successor that observed the foreign token
|
|
505
|
+
before reaching ready can clear it when that same successor becomes ready: reaching ready requires the
|
|
506
|
+
stable backend runner lock, so the predecessor's reset can no longer be active. A generation that was
|
|
507
|
+
already ready when it observed the token must acquire the runner lock before clearing it. Recovery waits
|
|
508
|
+
if a writer still owns that lock.
|
|
509
|
+
The pause covers every reader on the physical path, including a superseded generation. Each active reset
|
|
510
|
+
retains its own token; stale resumes cannot clear newer tokens. A worker with no query index on the path
|
|
511
|
+
retains no token because it has no reader to fence, and any later attachment remains gated by its
|
|
512
|
+
generation's shared readiness.
|
|
513
|
+
After the last local attachment for a superseded readiness id drains and unregisters, Harper clears
|
|
514
|
+
that id's exact-epoch token from surviving readers on the path and remembers that the id retired. A
|
|
515
|
+
delayed pause for that retired id cannot reinstall its fence on a successor. A readiness id never seen
|
|
516
|
+
on this worker is still accepted, so a successor reset fences a predecessor reader on a worker whose
|
|
517
|
+
schema refresh is late. Closing attachments remain registered until their readers drain, and a duplicate
|
|
518
|
+
attachment for the same readiness id keeps the fence. This closes both handoff directions without
|
|
519
|
+
assuming an ordering between generation-local epochs. Retirement uses an explicit synthetic token that
|
|
520
|
+
still pauses every reader because it is not tied to a generation attachment. If the native
|
|
521
|
+
reader violates its contract by rejecting close, Harper logs the failure and proceeds with reset
|
|
522
|
+
rather than wedging the path indefinitely; the reset therefore assumes that rejected handle is dead.
|
|
523
|
+
|
|
524
|
+
Weights and highlighting are query configuration. Changing either refreshes readers without rotating
|
|
525
|
+
the persisted generation. Analyzer behavior, stop words, positions, surface terms, synonyms, source
|
|
526
|
+
media types, source-field membership and source order are storage identity and require a new generation. The
|
|
527
|
+
wrapper validates that identity on inspection and open, so an older analyzer generation fails closed
|
|
528
|
+
and rebuilds instead of serving mixed tokenization semantics.
|
|
529
|
+
|
|
530
|
+
Highlighting is declaration-controlled and off unless configured. Snippet fragments contain source
|
|
531
|
+
plaintext, so Harper authorizes every highlighted source field before search; selecting
|
|
532
|
+
`$highlights` is also selecting that configured plaintext egress. Harper bounds each native trace call
|
|
533
|
+
by both record count and encoded source bytes and sends only the fields used by that query leaf. A
|
|
534
|
+
single record larger than the native trace limit is rejected before crossing the binding.
|
|
535
|
+
|
|
536
|
+
Search hits carry the source record version. Harper loads the authoritative record through its read
|
|
537
|
+
transaction and omits a hit when that version no longer matches; it never attaches an old score or
|
|
538
|
+
highlight to new content. Bounded searches over-fetch at least 32 and at most 256 native hits per
|
|
539
|
+
page, growing through the native result window only when version checks or structured filters reject
|
|
540
|
+
candidates. Exhausting that window because native versions are stale is retryable index lag; filter-
|
|
541
|
+
only exhaustion remains a client error asking for a narrower query. During bounded lag a smaller
|
|
542
|
+
result can still temporarily omit a recently changed record when the native result set ends before
|
|
543
|
+
the search window is exhausted.
|
|
544
|
+
REST exposes the index coverage header, and callers that require current coverage use
|
|
545
|
+
`maxIndexLagMilliseconds: 0` or `waitForIndexMilliseconds`.
|
|
546
|
+
Full-text score descending is the only supported ordering in this release. Count requests return
|
|
547
|
+
`recordCount: null` and `recordCountExact: false`; the native candidate total cannot become an exact
|
|
548
|
+
Harper count after authorization, structured filtering and source-version checks.
|
|
549
|
+
|
|
550
|
+
A declared Blob source is part of one index document. An oversized, invalid UTF-8 or otherwise
|
|
551
|
+
permanently unusable Blob makes that whole document unindexable rather than publishing a partial
|
|
552
|
+
document with different match semantics. A transient Blob read failure rolls back the accepted
|
|
553
|
+
native batch and replays it. Each source read is time-bounded and replay uses the writer's bounded
|
|
554
|
+
backoff, but an unreadable authoritative record never becomes an index deletion. Publication remains
|
|
555
|
+
behind that record until its source is readable, preserving cursor and publication atomicity at the
|
|
556
|
+
cost of delaying unrelated records in the batch. The normal derived-index lag policy makes prolonged
|
|
557
|
+
failure visible and eventually applies write backpressure rather than silently diverging replicas.
|
|
409
558
|
|
|
410
559
|
### Bounded delivery
|
|
411
560
|
|
|
@@ -570,6 +719,9 @@ health transitions. An owner refreshes idle coverage at its flush cadence withou
|
|
|
570
719
|
release deadline. A query within the certified age bound reads only shared memory and the monotonic
|
|
571
720
|
clock; strict or older queries compare the persisted vector with current physical positions. This also
|
|
572
721
|
certifies an unchanged index after owner release or process restart, when no usable time proof remains.
|
|
722
|
+
An old owner can race with a non-ready transition and restore an earlier coverage time, but consumers
|
|
723
|
+
gate the time on stable ready readiness; after rebuild, the rebuilt anchor still covers that earlier true
|
|
724
|
+
prefix.
|
|
573
725
|
Concurrent waiters on one worker/index share a single 25 ms poll timer, removed when all resolve,
|
|
574
726
|
time out, or abort. A first waiter nudges its local runner without changing writer-lag accounting or
|
|
575
727
|
retrying a deferred batch; an active peer owner already refreshes at flush cadence. No cross-worker
|
|
@@ -4,6 +4,7 @@ import { FLOAT32_OPTIONS, pack, unpack } from 'msgpackr';
|
|
|
4
4
|
import { loggerWithTag } from '../../utility/logging/logger.ts';
|
|
5
5
|
import { ClientError, ServerError, DerivedIndexLagError } from '../../utility/errors/hdbError.ts';
|
|
6
6
|
import type { Id } from '../../resources/ResourceInterface.ts';
|
|
7
|
+
import type { CandidateKeyPlan, CandidateKeySet } from '../search.ts';
|
|
7
8
|
import {
|
|
8
9
|
DEFAULT_MAX_INDEX_LAG_MILLISECONDS,
|
|
9
10
|
MAX_WAIT_FOR_INDEX_MILLISECONDS,
|
|
@@ -269,6 +270,20 @@ const MAX_LEVEL = 10; // should give good high-level skip list performance up to
|
|
|
269
270
|
// islands are small (bounded by the deleted node's neighborhood), so a probe that visits this many
|
|
270
271
|
// nodes without reaching the entry point is treated as connected-but-far and repaired conservatively.
|
|
271
272
|
const PROBE_VISIT_LIMIT = 256;
|
|
273
|
+
// Candidate keys one predicate visit pays for: a key-only index-entry read against a record load
|
|
274
|
+
// and decode. Measured warm at 0.37 us against 2.1 us, rounded up from that 5.7; the same load is
|
|
275
|
+
// ~130 us cold, so the warm end is the regression-safe calibration — over-stating the ratio would
|
|
276
|
+
// build allow-sets for broad filters the predicate already answers cheaply. See DESIGN.md.
|
|
277
|
+
const KEYS_PER_PREDICATE_VISIT = 8;
|
|
278
|
+
// Absolute ceiling on index entries one query may read building an allow-set, whatever the cost
|
|
279
|
+
// model allows. The scan is synchronous, so this bounds both the event-loop stall and the retained
|
|
280
|
+
// key set; past it the predicate path — which is incremental and budget-bounded — is the safer cost.
|
|
281
|
+
const MAX_CANDIDATE_KEYS = 50_000;
|
|
282
|
+
// Filling ef matches at selectivity s costs ~ef/s visits; this is the slack over that.
|
|
283
|
+
const ALLOW_SET_BUDGET_SLACK = 2;
|
|
284
|
+
// How far an allow-set may widen the filterExpansion visit budget. A visit got cheaper, not free:
|
|
285
|
+
// the JS graph still loads a node and computes a distance for each one, synchronously.
|
|
286
|
+
const ALLOW_SET_BUDGET_MAX_WIDENING = 4;
|
|
272
287
|
type Connection = {
|
|
273
288
|
id: number;
|
|
274
289
|
distance: number;
|
|
@@ -437,6 +452,10 @@ export class HierarchicalNavigableSmallWorld {
|
|
|
437
452
|
// during traversal (predicate-aware / ACORN-style filtering), so companion conditions and RBAC can
|
|
438
453
|
// be pushed down instead of post-filtering an under-filled candidate set (#1241).
|
|
439
454
|
filteredSearch = true;
|
|
455
|
+
// Signals that search() also accepts a CandidateKeyPlan and admits from it directly, so an indexed
|
|
456
|
+
// companion condition costs a key-only index scan per matching record instead of a record load and
|
|
457
|
+
// decode per visited node. See DESIGN.md.
|
|
458
|
+
candidateKeyFilter = true;
|
|
440
459
|
indexStore: any;
|
|
441
460
|
M: number = 16; // max number of connections per layer
|
|
442
461
|
efConstruction: number = 100; // size of dynamic candidate list
|
|
@@ -457,6 +476,10 @@ export class HierarchicalNavigableSmallWorld {
|
|
|
457
476
|
// latency for recall on selective function predicates; 24 fills to roughly 4% selectivity before
|
|
458
477
|
// the budget binds. Per-query override via the search options.
|
|
459
478
|
filterExpansion = 24;
|
|
479
|
+
// Whether the schema pinned filterExpansion. An explicit pin is an authoritative cost ceiling and
|
|
480
|
+
// keeps its exact meaning on every path; only an index that never set it gets the
|
|
481
|
+
// selectivity-derived budget an allow-set makes measurable.
|
|
482
|
+
filterExpansionConfigured = false;
|
|
460
483
|
|
|
461
484
|
idIncrementer: BigInt64Array | undefined;
|
|
462
485
|
distance: (a: number[], b: number[]) => number;
|
|
@@ -534,7 +557,10 @@ export class HierarchicalNavigableSmallWorld {
|
|
|
534
557
|
if (configuredEfConstructionSearch !== undefined) this.efConstructionSearch = configuredEfConstructionSearch;
|
|
535
558
|
if (configuredML !== undefined) this.mL = configuredML;
|
|
536
559
|
if (configuredOptimizeRouting !== undefined) this.optimizeRouting = configuredOptimizeRouting;
|
|
537
|
-
if (configuredFilterExpansion !== undefined)
|
|
560
|
+
if (configuredFilterExpansion !== undefined) {
|
|
561
|
+
this.filterExpansion = configuredFilterExpansion;
|
|
562
|
+
this.filterExpansionConfigured = true;
|
|
563
|
+
}
|
|
538
564
|
}
|
|
539
565
|
if (options?.nativePlane) {
|
|
540
566
|
const { nativeM, nativeEfConstruction, nativeML, nativeOptimizeRouting, maxNodes, keyCap, layer0Cap } =
|
|
@@ -847,6 +873,52 @@ export class HierarchicalNavigableSmallWorld {
|
|
|
847
873
|
});
|
|
848
874
|
}
|
|
849
875
|
|
|
876
|
+
/**
|
|
877
|
+
* Run a candidate-key plan when its scans are cheaper than the predicate visits they replace, and
|
|
878
|
+
* return the matching primary keys with whether they alone decide admission. Undefined means
|
|
879
|
+
* "take the predicate path": the plan was too broad, its narrowest term overran the scan budget,
|
|
880
|
+
* or the scan failed — none of which is a fault of the graph.
|
|
881
|
+
*/
|
|
882
|
+
private collectCandidateKeys(
|
|
883
|
+
plan: CandidateKeyPlan,
|
|
884
|
+
ef: number,
|
|
885
|
+
predicateMaxVisits: number
|
|
886
|
+
): { keys: CandidateKeySet; complete: boolean } | undefined {
|
|
887
|
+
const nodeCount = this.approximateNodeCount();
|
|
888
|
+
const estimated = Math.max(1, plan.estimatedCount);
|
|
889
|
+
// Filling ef matches at selectivity s costs ~ef/s visits, and s is estimated/nodeCount, so a
|
|
890
|
+
// broad filter needs few predicate visits however cheap the test that would replace them.
|
|
891
|
+
const predicateVisits = Math.min(predicateMaxVisits, (ef * nodeCount) / estimated);
|
|
892
|
+
let maxKeys = Math.min(Math.ceil(predicateVisits * KEYS_PER_PREDICATE_VISIT), MAX_CANDIDATE_KEYS);
|
|
893
|
+
// An allow-set over a large fraction of the graph is not a filter worth building.
|
|
894
|
+
if (nodeCount > 0) maxKeys = Math.min(maxKeys, Math.max(1, nodeCount >> 1));
|
|
895
|
+
if (estimated > maxKeys) return undefined;
|
|
896
|
+
try {
|
|
897
|
+
return plan.collect(maxKeys) ?? undefined;
|
|
898
|
+
} catch (error) {
|
|
899
|
+
// A sibling index that started rebuilding or closed between planning and the scan is a
|
|
900
|
+
// reason to filter the other way, not to fail — and not to reach the caller's handler,
|
|
901
|
+
// which reads an unclassified search error as plane corruption and requests a rebuild.
|
|
902
|
+
logger.warn?.('could not build the HNSW candidate-key allow-set; using the predicate path', error);
|
|
903
|
+
return undefined;
|
|
904
|
+
}
|
|
905
|
+
}
|
|
906
|
+
|
|
907
|
+
/**
|
|
908
|
+
* Visit budget for a traversal an allow-set decides, derived from the selectivity the set makes
|
|
909
|
+
* measurable rather than from filterExpansion — which exists to bound an admission test expensive
|
|
910
|
+
* enough to be worth bounding, and this one is a set lookup. It only ever widens the
|
|
911
|
+
* filterExpansion budget, never narrows it: a cheaper visit makes a tighter budget buy nothing
|
|
912
|
+
* and cost recall. An empty set keeps that budget, so a zero-match filter returns empty after a
|
|
913
|
+
* bounded descent rather than crawling the graph.
|
|
914
|
+
*/
|
|
915
|
+
private allowSetVisitBudget(allowed: number, predicateMaxVisits: number, budgetEf: number): number {
|
|
916
|
+
if (allowed <= 0) return predicateMaxVisits;
|
|
917
|
+
const selectivity = Math.min(1, allowed / Math.max(1, this.approximateNodeCount()));
|
|
918
|
+
const derived = budgetEf * Math.ceil(ALLOW_SET_BUDGET_SLACK / selectivity);
|
|
919
|
+
return Math.min(Math.max(predicateMaxVisits, derived), predicateMaxVisits * ALLOW_SET_BUDGET_MAX_WIDENING);
|
|
920
|
+
}
|
|
921
|
+
|
|
850
922
|
attachDerivedHost(host: DerivedNativeIndexHost): void {
|
|
851
923
|
this.derivedHost = host;
|
|
852
924
|
}
|
|
@@ -1796,7 +1868,9 @@ export class HierarchicalNavigableSmallWorld {
|
|
|
1796
1868
|
* cheap even when the predicate loads a record.
|
|
1797
1869
|
*/
|
|
1798
1870
|
private admit(filter: (primaryKey: Id) => boolean, filterState: FilterState | undefined, primaryKey: Id): boolean {
|
|
1799
|
-
|
|
1871
|
+
// A filter fronted by an allow-set counts its own evaluations: the counter reports how much of
|
|
1872
|
+
// the graph a filtered query had to LOAD, and a membership test loads nothing.
|
|
1873
|
+
if (filterState && !filterState.countsOwnEvaluations) filterState.filterEvaluations++;
|
|
1800
1874
|
return filter(primaryKey);
|
|
1801
1875
|
}
|
|
1802
1876
|
|
|
@@ -1839,6 +1913,7 @@ export class HierarchicalNavigableSmallWorld {
|
|
|
1839
1913
|
{
|
|
1840
1914
|
filter,
|
|
1841
1915
|
minResults,
|
|
1916
|
+
candidateKeys,
|
|
1842
1917
|
}: {
|
|
1843
1918
|
// Predicate-aware traversal (#1241). When provided, only nodes for which `filter(primaryKey)`
|
|
1844
1919
|
// returns true are admitted to the result list at layer 0; routing is unaffected. Composed by
|
|
@@ -1850,6 +1925,8 @@ export class HierarchicalNavigableSmallWorld {
|
|
|
1850
1925
|
// (AUTO_EF_MAX) however large the limit was. Raising ef to cover the request keeps `limit`
|
|
1851
1926
|
// meaningful; the caller pays for what it asked for.
|
|
1852
1927
|
minResults?: number;
|
|
1928
|
+
// Always optional: the predicate path is the fallback for every plan this declines.
|
|
1929
|
+
candidateKeys?: CandidateKeyPlan;
|
|
1853
1930
|
} = {}
|
|
1854
1931
|
) {
|
|
1855
1932
|
const waiting = this.filePrimary && waitForIndexMilliseconds > 0;
|
|
@@ -1930,15 +2007,53 @@ export class HierarchicalNavigableSmallWorld {
|
|
|
1930
2007
|
// second-regime search ef (up to AUTO_EF_CEILING) must not silently quadruple the filtered
|
|
1931
2008
|
// worst case — the recall decision and the filtered-scan budget are separate decisions. Callers
|
|
1932
2009
|
// wanting a deeper filtered search set an explicit ef or filterExpansion, and own the cost.
|
|
2010
|
+
const budgetEf = explicitEf || this.efSearchConfigured ? resolvedEf : Math.min(resolvedEf, AUTO_EF_MAX);
|
|
2011
|
+
const predicateMaxVisits =
|
|
2012
|
+
budgetEf * (filterExpansion && filterExpansion > 0 ? filterExpansion : this.filterExpansion);
|
|
2013
|
+
const allowedKeys =
|
|
2014
|
+
filter && candidateKeys ? this.collectCandidateKeys(candidateKeys, resolvedEf, predicateMaxVisits) : undefined;
|
|
2015
|
+
const allowDecides = Boolean(allowedKeys?.complete);
|
|
1933
2016
|
const filterState: FilterState | undefined = filter
|
|
1934
2017
|
? {
|
|
1935
|
-
maxVisits:
|
|
1936
|
-
(explicitEf || this.efSearchConfigured ? resolvedEf : Math.min(resolvedEf, AUTO_EF_MAX)) *
|
|
1937
|
-
(filterExpansion && filterExpansion > 0 ? filterExpansion : this.filterExpansion),
|
|
2018
|
+
maxVisits: predicateMaxVisits,
|
|
1938
2019
|
nodesVisited: 0,
|
|
1939
2020
|
filterEvaluations: 0,
|
|
2021
|
+
candidateKeys: allowedKeys?.keys.size,
|
|
1940
2022
|
}
|
|
1941
2023
|
: undefined;
|
|
2024
|
+
if (allowedKeys && filter && filterState) {
|
|
2025
|
+
// Both traversals admit by primary key — the plane hands its predicate the key of every
|
|
2026
|
+
// candidate — so the set is a membership test in front of the record load, and a complete
|
|
2027
|
+
// plan removes the load entirely.
|
|
2028
|
+
const allowed = allowedKeys.keys;
|
|
2029
|
+
const admits = (primaryKey: Id) => allowed.has(primaryKey);
|
|
2030
|
+
const residual = filter;
|
|
2031
|
+
const state = filterState;
|
|
2032
|
+
state.countsOwnEvaluations = true;
|
|
2033
|
+
filter = allowDecides
|
|
2034
|
+
? admits
|
|
2035
|
+
: (primaryKey: Id) => {
|
|
2036
|
+
if (!admits(primaryKey)) return false;
|
|
2037
|
+
state.filterEvaluations++;
|
|
2038
|
+
return residual(primaryKey);
|
|
2039
|
+
};
|
|
2040
|
+
// Only a filter the set DECIDES gets the derived budget: an admitted visit on the residual
|
|
2041
|
+
// path still costs a record decode, so that path keeps today's ceiling. A positive per-query
|
|
2042
|
+
// or any schema-configured filterExpansion is an authoritative cost ceiling and is used
|
|
2043
|
+
// exactly as given; a per-query 0 still means unset.
|
|
2044
|
+
if (allowDecides) {
|
|
2045
|
+
const configured =
|
|
2046
|
+
filterExpansion && filterExpansion > 0
|
|
2047
|
+
? filterExpansion
|
|
2048
|
+
: this.filterExpansionConfigured
|
|
2049
|
+
? this.filterExpansion
|
|
2050
|
+
: undefined;
|
|
2051
|
+
state.maxVisits =
|
|
2052
|
+
configured === undefined
|
|
2053
|
+
? this.allowSetVisitBudget(allowedKeys.keys.size, predicateMaxVisits, budgetEf)
|
|
2054
|
+
: budgetEf * configured;
|
|
2055
|
+
}
|
|
2056
|
+
}
|
|
1942
2057
|
const rerankK =
|
|
1943
2058
|
comparator === 'sort' && minResults !== undefined
|
|
1944
2059
|
? Math.min(effectiveEf, Math.max(RERANK_MIN, minResults * RERANK_FACTOR))
|
|
@@ -2328,6 +2443,9 @@ type FilterState = {
|
|
|
2328
2443
|
maxVisits: number;
|
|
2329
2444
|
nodesVisited: number;
|
|
2330
2445
|
filterEvaluations: number;
|
|
2446
|
+
candidateKeys?: number;
|
|
2447
|
+
// The filter counts its own record-loading evaluations, so admit() must not count again.
|
|
2448
|
+
countsOwnEvaluations?: boolean;
|
|
2331
2449
|
};
|
|
2332
2450
|
/**
|
|
2333
2451
|
* Attach filtered-traversal counters to the returned entries array so callers (search.ts / explain) can
|
|
@@ -2338,6 +2456,8 @@ function withStats<T extends any[]>(entries: T, filterState: FilterState | undef
|
|
|
2338
2456
|
if (filterState) {
|
|
2339
2457
|
Object.defineProperty(entries, 'nodesVisited', { value: filterState.nodesVisited, enumerable: false });
|
|
2340
2458
|
Object.defineProperty(entries, 'filterEvaluations', { value: filterState.filterEvaluations, enumerable: false });
|
|
2459
|
+
if (filterState.candidateKeys !== undefined)
|
|
2460
|
+
Object.defineProperty(entries, 'candidateKeys', { value: filterState.candidateKeys, enumerable: false });
|
|
2341
2461
|
}
|
|
2342
2462
|
return entries;
|
|
2343
2463
|
}
|