@harperfast/harper 5.2.12 → 5.2.14
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/components/Application.ts +7 -7
- package/components/componentLoader.ts +70 -33
- package/dist/components/Application.js +1 -7
- package/dist/components/Application.js.map +1 -1
- package/dist/components/componentLoader.d.ts +6 -0
- package/dist/components/componentLoader.js +60 -31
- package/dist/components/componentLoader.js.map +1 -1
- package/dist/resources/RocksTransactionLogStore.d.ts +4 -2
- package/dist/resources/RocksTransactionLogStore.js +12 -6
- package/dist/resources/RocksTransactionLogStore.js.map +1 -1
- package/dist/resources/Table.d.ts +2 -2
- package/dist/resources/Table.js +222 -65
- package/dist/resources/Table.js.map +1 -1
- package/dist/resources/replayLogs.js +25 -21
- package/dist/resources/replayLogs.js.map +1 -1
- package/dist/resources/transactionBroadcast.js +4 -6
- package/dist/resources/transactionBroadcast.js.map +1 -1
- package/dist/security/certificateVerification/index.js +40 -11
- package/dist/security/certificateVerification/index.js.map +1 -1
- package/dist/security/certificateVerification/trustedIssuers.d.ts +24 -0
- package/dist/security/certificateVerification/trustedIssuers.js +79 -0
- package/dist/security/certificateVerification/trustedIssuers.js.map +1 -0
- package/dist/security/certificateVerification/types.d.ts +1 -0
- package/dist/security/keys.js +4 -0
- package/dist/security/keys.js.map +1 -1
- package/dist/server/loadRootComponents.js +4 -2
- package/dist/server/loadRootComponents.js.map +1 -1
- package/dist/server/threads/manageThreads.d.ts +1 -0
- package/dist/server/threads/manageThreads.js +175 -24
- package/dist/server/threads/manageThreads.js.map +1 -1
- package/dist/utility/errors/hdbError.d.ts +8 -0
- package/dist/utility/errors/hdbError.js +29 -0
- package/dist/utility/errors/hdbError.js.map +1 -1
- package/npm-shrinkwrap.json +2 -2
- package/package.json +1 -1
- package/resources/DESIGN.md +5 -0
- package/resources/RocksTransactionLogStore.ts +12 -6
- package/resources/Table.ts +216 -66
- package/resources/replayLogs.ts +25 -22
- package/resources/transactionBroadcast.ts +4 -4
- package/security/certificateVerification/index.ts +54 -13
- package/security/certificateVerification/trustedIssuers.ts +76 -0
- package/security/certificateVerification/types.ts +1 -0
- package/security/keys.ts +3 -0
- package/server/DESIGN.md +14 -13
- package/server/loadRootComponents.js +9 -2
- package/server/threads/manageThreads.js +176 -23
- package/studio/web/404.html +79 -0
- package/studio/web/assets/{Chat-CokJOjyX.js → Chat-C97PMlIn.js} +274 -247
- package/studio/web/assets/FloatingChat-BUlsFGMV.js +23 -0
- package/studio/web/assets/_basePickBy-QJXUw_pr.js +1 -0
- package/studio/web/assets/_baseUniq-DR4vVjyC.js +1 -0
- package/studio/web/assets/{abnfDiagram-VCTEODGH-B0BebmD2.js → abnfDiagram-O67JEVCF-DpPZWMOf.js} +1 -1
- package/studio/web/assets/{alertDialog-CQyAJJhl.js → alertDialog-UrbiMgQt.js} +1 -1
- package/studio/web/assets/{apiToken-B_g12QrM.js → apiToken-CPhbc_zN.js} +1 -1
- package/studio/web/assets/applications-D3Z6BVDl.js +296 -0
- package/studio/web/assets/architecture-7GRP2DOG-11l4Ny1l.js +1 -0
- package/studio/web/assets/architectureDiagram-NJMV4G6O-OmgLraID.js +36 -0
- package/studio/web/assets/authStore-5jSS5pTW.js +3 -0
- package/studio/web/assets/{blockDiagram-I7D4REHJ-BqguiadH.js → blockDiagram-BEXU5L5S-Cv7CD3Ai.js} +15 -4
- package/studio/web/assets/{button-BIsUKRZq.js → button-MFP9n8Ck.js} +1 -1
- package/studio/web/assets/c4Diagram-YGBWAQC7-Ds1vKYnN.js +38 -0
- package/studio/web/assets/channel-BDtouPd6.js +1 -0
- package/studio/web/assets/chevron-up-CFe8x5Bp.js +1 -0
- package/studio/web/assets/chunk-3FUC2YCW-DpnBPH8B.js +2 -0
- package/studio/web/assets/{chunk-GVQU2GXP-sbwVIQ8i.js → chunk-5DYCD2WN-LLLCQSzg.js} +1 -1
- package/studio/web/assets/{chunk-PWAF6VOD-1z1THyS5.js → chunk-742MDFTN-BL8J_pIO.js} +1 -1
- package/studio/web/assets/chunk-7INBJB4K-C-hJHiiK.js +62 -0
- package/studio/web/assets/chunk-7M6MHVWA-C6ZqkFbA.js +213 -0
- package/studio/web/assets/chunk-7PRAP22T-CUXM8BG6.js +1 -0
- package/studio/web/assets/chunk-DUW6YSOI-Dgx8z5s3.js +1 -0
- package/studio/web/assets/{chunk-FOHPRMQF-DHwB1DNv.js → chunk-FOHPRMQF-DzxwRta7.js} +9 -9
- package/studio/web/assets/{chunk-SVP7TREG-jtdAHw0S.js → chunk-GTNCS2PH-DJ2BFvS1.js} +1 -1
- package/studio/web/assets/{chunk-F27PBJKO-BVA5EPhV.js → chunk-GWA4HPMP-C42bSqLL.js} +1 -1
- package/studio/web/assets/chunk-J5ZVWO5B-oDCd-gWO.js +1 -0
- package/studio/web/assets/chunk-MBY4JIJT-C-5jz6Lo.js +72 -0
- package/studio/web/assets/chunk-NETBCI7D-D-bzlN4r.js +1 -0
- package/studio/web/assets/chunk-O7XYJQB3-ep14zA23.js +128 -0
- package/studio/web/assets/chunk-UA2S7LBM-BMRWRHED.js +1 -0
- package/studio/web/assets/{chunk-POPQ4Y6H-ClWhhkwW.js → chunk-WEXAMYUT-DdLYVdX1.js} +1 -1
- package/studio/web/assets/{chunk-XXDRQBXY-BoGZu3HH.js → chunk-XXDRQBXY-CbPh2l8S.js} +1 -1
- package/studio/web/assets/{chunk-OSK3NFVY-ByciRftO.js → chunk-Z7XXMR3K-BmKEDjcP.js} +3 -3
- package/studio/web/assets/{chunk-75Z2AOVW-QGQD6th2.js → chunk-ZIGJFQKS-DLZgdNxA.js} +2 -2
- package/studio/web/assets/classDiagram-v2-NBCMYWYE-DOI5vgKT.js +217 -0
- package/studio/web/assets/cose-bilkent-JH36ORCC-BSOszL11.js +1 -0
- package/studio/web/assets/createLucideIcon-bziYxJli.js +1 -0
- package/studio/web/assets/{css.worker-BD9HbRGW.js → css.worker-l1psR_wG.js} +13 -10
- package/studio/web/assets/{cssMode-DAjzPoBf.js → cssMode-DyLZPMjo.js} +1 -1
- package/studio/web/assets/cynefin-OW5HDTMX-BSW-DB7S.js +1 -0
- package/studio/web/assets/cynefinDiagram-VND7K2PF-CbAHEwxt.js +62 -0
- package/studio/web/assets/cytoscape.esm-BZkf-uXn.js +321 -0
- package/studio/web/assets/dagre-6A5THRUB-Csxewgs4.js +4 -0
- package/studio/web/assets/diagram-22UHCM2B-a0rt8tY4.js +200 -0
- package/studio/web/assets/{diagram-VX7I27RA-DccVJet6.js → diagram-3UASUU5V-lEj2SVNh.js} +2 -2
- package/studio/web/assets/{diagram-VSXAHHWV-fhEdmkwM.js → diagram-ATOU4E4O-HcrAeB-q.js} +3 -3
- package/studio/web/assets/{diagram-S7CK7UJ4-aoCVTtcy.js → diagram-CDSNMT55-CYxiuyEx.js} +3 -3
- package/studio/web/assets/{diagram-Z3DM3KII-D-RyJJb7.js → diagram-MLGK6HIB-ChbCJTgJ.js} +1 -1
- package/studio/web/assets/diagram-MPIPVDR6-DaX9Y-N3.js +41 -0
- package/studio/web/assets/dialog-DTepachC.js +1 -0
- package/studio/web/assets/{dist-kYfyPZP9.js → dist-BRuBKNNy.js} +1 -1
- package/studio/web/assets/{dist-DP8UjMB_.js → dist-Cz2Y6-6G.js} +2 -2
- package/studio/web/assets/download-CtostjYn.js +1 -0
- package/studio/web/assets/{ebnfDiagram-PWID7BFC-DJGpIpz_.js → ebnfDiagram-ZINNZB2B-CwAsENB1.js} +1 -1
- package/studio/web/assets/{editor-VUNQ-5Ye.js → editor-CFQ5kbiT.js} +1 -1
- package/studio/web/assets/editor.worker-CkkvdXvO.js +33 -0
- package/studio/web/assets/elk-276RUBZZ-Hexg9E4D.js +27 -0
- package/studio/web/assets/{erDiagram-RLTQ6QDP-CIfNlgkC.js → erDiagram-OPXOYQCR-CeC-wwO1.js} +15 -15
- package/studio/web/assets/eventmodeling-NTZA5JFV-CFb03e0o.js +1 -0
- package/studio/web/assets/flowDiagram-KWPJA3E3-Cidi00_Y.js +1 -0
- package/studio/web/assets/{ganttDiagram-EL5Y4UJY-BxToTzzD.js → ganttDiagram-FUAMR5RP-C2_Hhl1-.js} +2 -2
- package/studio/web/assets/{getAnalytics-GHK8ORfM.js → getAnalytics-CjthMQqF.js} +1 -1
- package/studio/web/assets/gitGraph-4MIJSDKK-xIk5Vuew.js +1 -0
- package/studio/web/assets/{gitGraphDiagram-WWUBYQGX-Dwntd4-x.js → gitGraphDiagram-X574FWY7-CMxJ34up.js} +4 -4
- package/studio/web/assets/graphlib-C9Tl7QrK.js +1 -0
- package/studio/web/assets/{html-CFqhtdfa.js → html-DdMBJEGu.js} +1 -1
- package/studio/web/assets/{html.worker-DsMmVtuG.js → html.worker-DdwM4IrA.js} +12 -9
- package/studio/web/assets/{htmlMode-CpgOTfjz.js → htmlMode-DNtBx3OV.js} +1 -1
- package/studio/web/assets/index-CPoDzQTe.css +1 -0
- package/studio/web/assets/index-zZCxAIEb.js +824 -0
- package/studio/web/assets/index.lazy-CKbsHbSN.js +2 -0
- package/studio/web/assets/info-A6RAGUB7-o49TEbj-.js +1 -0
- package/studio/web/assets/infoDiagram-VRGFBTTK-Cjhh7yzZ.js +2 -0
- package/studio/web/assets/{ishikawaDiagram-5VMMS53U-D9Xh2r6X.js → ishikawaDiagram-OU5B5YK6-C63aXRmh.js} +2 -2
- package/studio/web/assets/{javascript-9l5JXJwc.js → javascript-Dy6KbeHT.js} +1 -1
- package/studio/web/assets/{journeyDiagram-3NMN7TZE-CokIi6ll.js → journeyDiagram-ZHPQQLJL-D_o0P5Mu.js} +3 -3
- package/studio/web/assets/json.worker-CSi2hF-a.js +65 -0
- package/studio/web/assets/{jsonMode-BJBH-iR0.js → jsonMode-BiuDVrzl.js} +1 -1
- package/studio/web/assets/{kanban-definition-UXKFOSKX-CukSFJfX.js → kanban-definition-PNTS6WVX-CTI9SSxY.js} +7 -7
- package/studio/web/assets/{katex-CXMH3UgJ.js → katex-ZlcWpGUi.js} +2 -2
- package/studio/web/assets/languageServices-Dk8_6B25.js +1 -0
- package/studio/web/assets/linear-Ds1AeO4V.js +1 -0
- package/studio/web/assets/{lspLanguageFeatures-sNVDopx3.js → lspLanguageFeatures-DwJKXzQa.js} +2 -2
- package/studio/web/assets/{mermaid-parser.core-BlEsOWNO.js → mermaid-parser.core-C2lcH_jP.js} +3 -3
- package/studio/web/assets/mermaid.core-BG5bjfIG.js +44 -0
- package/studio/web/assets/{mindmap-definition-YA3MSWOX-IprMc_0j.js → mindmap-definition-NLK3R4M7-BK7ho6JD.js} +24 -24
- package/studio/web/assets/{notifications-BukjVuVe.js → notifications-DJqAtWxD.js} +1 -1
- package/studio/web/assets/{notifications-DvBJIIO0.js → notifications-DsDtb41c.js} +1 -1
- package/studio/web/assets/packet-AYTQ26CC-BHPy6KBV.js +1 -0
- package/studio/web/assets/{pegDiagram-XKGWAZYB-BNuPDLZY.js → pegDiagram-GJSIUBJH-DzjwG3jO.js} +1 -1
- package/studio/web/assets/pie-WAS4IAKB-D0cP-DTU.js +1 -0
- package/studio/web/assets/{pieDiagram-E7YTZNPT-DqNb6Ht2.js → pieDiagram-5QR66LMP-C_hOP9Lv.js} +2 -2
- package/studio/web/assets/profile-zTGUN51x.js +1 -0
- package/studio/web/assets/{quadrantDiagram-AXDQQJYC-BGH9E2YR.js → quadrantDiagram-O4NWA36T-CZW8PbV1.js} +3 -3
- package/studio/web/assets/radar-RG4KPBEZ-C8fk0U81.js +1 -0
- package/studio/web/assets/railroad-74A4TZTK-B60FWm17.js +1 -0
- package/studio/web/assets/railroad-abnf-HS5TGJTU-CLMVZvH0.js +1 -0
- package/studio/web/assets/railroad-ebnf-LZEXJU2U--tlwFoVB.js +1 -0
- package/studio/web/assets/railroad-peg-WCYAUIDC-Dpuslblm.js +1 -0
- package/studio/web/assets/{railroadDiagram-O6MQD6OU-DGTPh2KZ.js → railroadDiagram-XR7U4H2S-DyyNmgbU.js} +1 -1
- package/studio/web/assets/regions-SOtABPB9.js +1 -0
- package/studio/web/assets/{register-CU-gCfLP.js → register-7JezVakc.js} +39 -39
- package/studio/web/assets/{requirementDiagram-BXWQKSXE-BJnO6uLz.js → requirementDiagram-PLB6GJNP-C4H-mLKu.js} +11 -11
- package/studio/web/assets/{rolldown-runtime-hePW80VL.js → rolldown-runtime-Dd_uD5pT.js} +1 -1
- package/studio/web/assets/sankeyDiagram-IPEJSGJF-DHDyga4c.js +40 -0
- package/studio/web/assets/sequenceDiagram-PO4LG4MO-CdNcHXRI.js +169 -0
- package/studio/web/assets/setComponentFile-BavlhfY0.js +1 -0
- package/studio/web/assets/{setup-DG3Ws0Gg.js → setup-04yVzr2I.js} +2 -2
- package/studio/web/assets/sizeCapture-INFHLROL-3uHXrFqA.js +1 -0
- package/studio/web/assets/{src-CMc7m_Ut.js → src-D0vMJQRr.js} +1 -1
- package/studio/web/assets/stateDiagram-v2-GCMORJYK-CpKuXq15.js +291 -0
- package/studio/web/assets/status-g_mNYE7j.js +61 -0
- package/studio/web/assets/swimlanes-2SLR337P-NRHa2D8I.js +1 -0
- package/studio/web/assets/swimlanesDiagram-TC7HE7FX-BBk4t4hO.js +8 -0
- package/studio/web/assets/{tabs-BrHu7gJi.js → tabs-CnQ_zenf.js} +1 -1
- package/studio/web/assets/{time-Cclkb7kQ.js → time-Ci1v4fvT.js} +1 -1
- package/studio/web/assets/timeline-definition-EJHVYXUP-D39Y-xpq.js +120 -0
- package/studio/web/assets/toggleHighContrast-i2FjCrzO.js +49 -0
- package/studio/web/assets/treeView-Q6P3EWNA-CRWR9OqD.js +1 -0
- package/studio/web/assets/treemap-WGGIJYW6-ZfXyJgLO.js +1 -0
- package/studio/web/assets/{ts.worker-DpaaFKhy.js → ts.worker-DDMUrpqh.js} +118 -115
- package/studio/web/assets/{tsMode-D3T_5QzW.js → tsMode-DGeWfx0W.js} +5 -5
- package/studio/web/assets/{typescript-BegjUvtB.js → typescript-62_1FUbs.js} +1 -1
- package/studio/web/assets/{useEntityRestURL-BicFpx_N.js → useEntityRestURL-DSVwev9J.js} +1 -1
- package/studio/web/assets/useLocalStorage-BYoZF60G.js +1 -0
- package/studio/web/assets/usecaseDiagram-POWQR4AR-jf-zIDZ_.js +328 -0
- package/studio/web/assets/vendor-core-BdkdbUek.js +63 -0
- package/studio/web/assets/vendor-datadog-fCQhBHGk.js +6 -0
- package/studio/web/assets/vendor-react-izWchTRL.js +9 -0
- package/studio/web/assets/vendor-tanstack-CQ1aRjVa.js +2 -0
- package/studio/web/assets/vendor-ui-aE1tHcW7.js +41 -0
- package/studio/web/assets/vennDiagram-UO4OBE2U-BKYAZUh_.js +34 -0
- package/studio/web/assets/wardley-WFR3VGLG-BO7f36ci.js +1 -0
- package/studio/web/assets/{wardleyDiagram-VM6X3IG4-CjrkKWUR.js → wardleyDiagram-VNRHLVJA-Djb0JDQc.js} +3 -3
- package/studio/web/assets/{workers-Bmx4l_9B.js → workers-Cs52GyHv.js} +1 -1
- package/studio/web/assets/x-vYntCxm4.js +1 -0
- package/studio/web/assets/{xml-Bg40Rcra.js → xml-Co5ow_Ix.js} +1 -1
- package/studio/web/assets/{xychartDiagram-S5SC5T6Z-Biok4GYV.js → xychartDiagram-PMCCYNJV-BQh9ubvb.js} +3 -3
- package/studio/web/assets/{yaml-DolDvfXE.js → yaml-BcngpXye.js} +1 -1
- package/studio/web/assets/yaml.worker-DAK2o_H1.js +479 -0
- package/studio/web/auth/fabric-globe-dark.png +0 -0
- package/studio/web/auth/fabric-globe-light.png +0 -0
- package/studio/web/auth/fabric-platform-dark.png +0 -0
- package/studio/web/auth/fabric-platform-light.png +0 -0
- package/studio/web/index.html +15 -15
- package/utility/errors/hdbError.ts +26 -0
- package/studio/web/assets/FloatingChat-h9BJo6YG.js +0 -23
- package/studio/web/assets/applications-Dlq5kJ2O.js +0 -296
- package/studio/web/assets/architecture-7GRP2DOG-LB-MLAAb.js +0 -1
- package/studio/web/assets/architectureDiagram-5GKGNRK7-7SW3GD-K.js +0 -36
- package/studio/web/assets/authStore-C3Nfubqr.js +0 -3
- package/studio/web/assets/c4Diagram-7LVT6UL2-LBNf8t_X.js +0 -38
- package/studio/web/assets/channel-yictG-U-.js +0 -1
- package/studio/web/assets/chevron-up-DtKGqDn3.js +0 -1
- package/studio/web/assets/chunk-4HAMMTFA-DWtTut21.js +0 -62
- package/studio/web/assets/chunk-DU6HZSFF-Chq20Ba5.js +0 -127
- package/studio/web/assets/chunk-GMAD6QVW-BeS7S07A.js +0 -72
- package/studio/web/assets/chunk-IMKFNOWR-Bnh3tAVd.js +0 -231
- package/studio/web/assets/chunk-L3NEJ4N5-COfUyKII.js +0 -1
- package/studio/web/assets/chunk-P2QGCYS3-CP1VhG_c.js +0 -1
- package/studio/web/assets/chunk-SHT3W25Y-LpQkMsah.js +0 -168
- package/studio/web/assets/chunk-TICWLB2K-VOwzetX-.js +0 -206
- package/studio/web/assets/classDiagram-ZZMXUADV-VaEwSy_g.js +0 -1
- package/studio/web/assets/classDiagram-v2-VYDZK3BY-VaEwSy_g.js +0 -1
- package/studio/web/assets/cose-bilkent-JH36ORCC-Cuuf67Bh.js +0 -1
- package/studio/web/assets/createLucideIcon-CzW9508A.js +0 -1
- package/studio/web/assets/cynefin-OW5HDTMX-BbdbCvub.js +0 -1
- package/studio/web/assets/cynefinDiagram-5FMLGOSQ-TP-aIqbt.js +0 -62
- package/studio/web/assets/cytoscape.esm-C2x7inlt.js +0 -321
- package/studio/web/assets/dagre-GXQ25YYZ-DShnGpGo.js +0 -4
- package/studio/web/assets/dagre-PrKaheQc.js +0 -1
- package/studio/web/assets/diagram-UQ7AKVKN-DglXtQ6x.js +0 -41
- package/studio/web/assets/dialog-Cn2uWgD4.js +0 -1
- package/studio/web/assets/download-B5T5r7ss.js +0 -1
- package/studio/web/assets/editor.worker-DwcOiOeP.js +0 -30
- package/studio/web/assets/eventmodeling-NTZA5JFV-CLxnp2CR.js +0 -1
- package/studio/web/assets/flowDiagram-HODETNUW-BIbhmz9f.js +0 -1
- package/studio/web/assets/gitGraph-4MIJSDKK-D2s2w8lE.js +0 -1
- package/studio/web/assets/graphlib-DS17s2tU.js +0 -1
- package/studio/web/assets/index-7RMEgVG1.css +0 -1
- package/studio/web/assets/index-ZQ1upnh7.js +0 -824
- package/studio/web/assets/index.lazy-HDafSRxx.js +0 -2
- package/studio/web/assets/info-A6RAGUB7-DYjkvb0C.js +0 -1
- package/studio/web/assets/infoDiagram-27XIBGKW-Bnp1FJE5.js +0 -2
- package/studio/web/assets/json.worker-fO_hOLCP.js +0 -62
- package/studio/web/assets/languageServices-0ZvgO63b.js +0 -1
- package/studio/web/assets/linear-Cnt8506Q.js +0 -1
- package/studio/web/assets/mermaid.core-BlkGaMIH.js +0 -44
- package/studio/web/assets/packet-AYTQ26CC-Bi3V04Zi.js +0 -1
- package/studio/web/assets/pie-WAS4IAKB-_6DoDbng.js +0 -1
- package/studio/web/assets/profile-B-jyYdzV.js +0 -1
- package/studio/web/assets/radar-RG4KPBEZ-DDdVczcL.js +0 -1
- package/studio/web/assets/railroad-74A4TZTK-BJUP4Jds.js +0 -1
- package/studio/web/assets/railroad-abnf-HS5TGJTU-Bm3L1L0h.js +0 -1
- package/studio/web/assets/railroad-ebnf-LZEXJU2U-CKzLGlkw.js +0 -1
- package/studio/web/assets/railroad-peg-WCYAUIDC-S8xLjslx.js +0 -1
- package/studio/web/assets/regions-GN6Mu5aH.js +0 -1
- package/studio/web/assets/sankeyDiagram-P5KCCOFB-0vSOdymH.js +0 -40
- package/studio/web/assets/sequenceDiagram-WJ2MYXX4-hETizDWE.js +0 -162
- package/studio/web/assets/setComponentFile-D7Fww0lC.js +0 -1
- package/studio/web/assets/sizeCapture-INFHLROL-B0uUizjq.js +0 -1
- package/studio/web/assets/stateDiagram-D77RDMKH-CdYQ_KtC.js +0 -1
- package/studio/web/assets/stateDiagram-v2-MP3YSRHH-CdKuzQMT.js +0 -1
- package/studio/web/assets/status-koJd66XF.js +0 -61
- package/studio/web/assets/swimlanes-42K2YHIH-B8cHIpU4.js +0 -1
- package/studio/web/assets/swimlanesDiagram-VR7AAH4N-DmOSJwaH.js +0 -8
- package/studio/web/assets/timeline-definition-24CTP7MA-BJWYSXqF.js +0 -120
- package/studio/web/assets/toggleHighContrast-CGksgPgv.js +0 -49
- package/studio/web/assets/treeView-Q6P3EWNA-qxe_v6CQ.js +0 -1
- package/studio/web/assets/treemap-WGGIJYW6-dDo97XXF.js +0 -1
- package/studio/web/assets/useLocalStorage-BqMR3D8_.js +0 -1
- package/studio/web/assets/vendor-core-c2JRRJpV.js +0 -58
- package/studio/web/assets/vendor-datadog-CLUcJXOo.js +0 -6
- package/studio/web/assets/vendor-react-CJV_K1u4.js +0 -9
- package/studio/web/assets/vendor-tanstack-DxzraizX.js +0 -1
- package/studio/web/assets/vendor-ui-BUjK0h8a.js +0 -41
- package/studio/web/assets/vennDiagram-4TSXK5OY-A3i-lCdl.js +0 -34
- package/studio/web/assets/wardley-WFR3VGLG-B0ik-_6g.js +0 -1
- package/studio/web/assets/x-DIzaLEdK.js +0 -1
- package/studio/web/assets/yaml.worker-BDL8_iuk.js +0 -454
package/resources/Table.ts
CHANGED
|
@@ -49,6 +49,7 @@ import {
|
|
|
49
49
|
ServerError,
|
|
50
50
|
AccessViolation,
|
|
51
51
|
ValidationError,
|
|
52
|
+
appendErrorContext,
|
|
52
53
|
type ValidationIssue,
|
|
53
54
|
} from '../utility/errors/hdbError.ts';
|
|
54
55
|
import * as signalling from '../utility/signalling.ts';
|
|
@@ -139,6 +140,20 @@ const EVICTION_BATCH_SIZE = 100;
|
|
|
139
140
|
// letting an unbounded number of open transactions (and their snapshots) accumulate.
|
|
140
141
|
const MAX_INFLIGHT_EVICTION_BATCHES = 4;
|
|
141
142
|
const CACHEABLE_STATUS_CODES = new Set([200, 203, 204, 206, 300, 301, 308, 404, 405, 410, 414, 501]);
|
|
143
|
+
// Smallest forward sample `getRecordCount` will extrapolate a record rate from; below it the scan runs
|
|
144
|
+
// to completion and reports an exact count.
|
|
145
|
+
const MIN_ESTIMATOR_SAMPLE = 1_000;
|
|
146
|
+
// Budget intervals the forward scan may spend before it must estimate rather than keep scanning.
|
|
147
|
+
const MAX_ESTIMATE_CHECKPOINTS = 20;
|
|
148
|
+
// A store estimate's `count`, or 0 when the store answered with a shape that cannot be trusted --
|
|
149
|
+
// DESIGN.md's invariant for this API family is that such an answer degrades rather than poisons.
|
|
150
|
+
function usableCount(estimate: any): number {
|
|
151
|
+
const { count, confidence } = estimate ?? {};
|
|
152
|
+
// `confidence` needs its own finiteness check, not just the range: `null >= 0 && null <= 1` is true
|
|
153
|
+
if (!Number.isFinite(count) || count < 0 || !Number.isFinite(confidence) || confidence < 0 || confidence > 1)
|
|
154
|
+
return 0;
|
|
155
|
+
return count;
|
|
156
|
+
}
|
|
142
157
|
envMngr.initSync();
|
|
143
158
|
const LMDB_PREFETCH_WRITES = envMngr.get(CONFIG_PARAMS.STORAGE_PREFETCHWRITES);
|
|
144
159
|
const LOCK_TIMEOUT = 10000;
|
|
@@ -1595,8 +1610,7 @@ export function makeTable(options) {
|
|
|
1595
1610
|
records: './', // an href to the records themselves
|
|
1596
1611
|
name: tableName,
|
|
1597
1612
|
database: databaseName,
|
|
1598
|
-
auditSize:
|
|
1599
|
-
auditStore instanceof RocksDatabase ? auditStore.getKeysCount() : auditStore?.getStats().entryCount,
|
|
1613
|
+
auditSize: auditStore?.getStats().entryCount,
|
|
1600
1614
|
attributes,
|
|
1601
1615
|
recordCount: undefined,
|
|
1602
1616
|
estimatedRecordRange: undefined,
|
|
@@ -2581,7 +2595,10 @@ export function makeTable(options) {
|
|
|
2581
2595
|
// getRange yields ascending by audit-log key, so the first entry is the oldest retained.
|
|
2582
2596
|
// Mirror replicationConnection's retention check and the cleanup key basis (localTime ??
|
|
2583
2597
|
// version). Fall back to the nominal time-based purge floor when the log is empty/unavailable.
|
|
2584
|
-
for (const entry of auditStore.getRange({
|
|
2598
|
+
for (const entry of auditStore.getRange({
|
|
2599
|
+
start: 1,
|
|
2600
|
+
log: auditStore.logFor(options?.nodeId, options?.viaNodeId).name,
|
|
2601
|
+
})) {
|
|
2585
2602
|
oldestRetainedAuditTime = entry.localTime ?? entry.version;
|
|
2586
2603
|
break;
|
|
2587
2604
|
}
|
|
@@ -2605,7 +2622,7 @@ export function makeTable(options) {
|
|
|
2605
2622
|
// A recommit of the same transaction survived that skip only because the old write batch
|
|
2606
2623
|
// still carried the put; a fresh-transaction replay (ERR_TRY_AGAIN) would drop the write.
|
|
2607
2624
|
if (isRocksDB && !stagedOwnAuditEntry && dedupVersionCouldBeRetained(txnTime)) {
|
|
2608
|
-
const priorAudit = auditStore.get(txnTime, tableId, id, options?.nodeId);
|
|
2625
|
+
const priorAudit = auditStore.get(txnTime, tableId, id, options?.nodeId, options?.viaNodeId);
|
|
2609
2626
|
if (
|
|
2610
2627
|
priorAudit &&
|
|
2611
2628
|
priorAudit.version === txnTime &&
|
|
@@ -2669,7 +2686,7 @@ export function makeTable(options) {
|
|
|
2669
2686
|
const isReDeliveredDuplicate = () => {
|
|
2670
2687
|
if (stagedOwnAuditEntry) return false;
|
|
2671
2688
|
if (!dedupVersionCouldBeRetained(txnTime)) return false; // pre-retention version — skip the end-of-log scan (best-effort; see above)
|
|
2672
|
-
const duplicate = auditStore.get(txnTime, tableId, id, options?.nodeId);
|
|
2689
|
+
const duplicate = auditStore.get(txnTime, tableId, id, options?.nodeId, options?.viaNodeId);
|
|
2673
2690
|
return (
|
|
2674
2691
|
duplicate &&
|
|
2675
2692
|
duplicate.version === txnTime &&
|
|
@@ -3135,6 +3152,7 @@ export function makeTable(options) {
|
|
|
3135
3152
|
commit: (txnTime, existingEntry, retry, transaction: any) => {
|
|
3136
3153
|
write.stagedEntry = undefined; // reset per round; set below once the removal is applied
|
|
3137
3154
|
write.superseded = false; // reset per round, as in the update path
|
|
3155
|
+
write.skipped = false;
|
|
3138
3156
|
// what a preceding write in this transaction left for this key is what gets removed
|
|
3139
3157
|
// from the indices here, not the pre-transaction record (harper#1968)
|
|
3140
3158
|
const priorStagedOp = priorStagedWrite(write);
|
|
@@ -3153,6 +3171,20 @@ export function makeTable(options) {
|
|
|
3153
3171
|
if (precedesExistingVersion(txnTime, existingEntry, options?.nodeId) < 0) {
|
|
3154
3172
|
return;
|
|
3155
3173
|
}
|
|
3174
|
+
const removalNodeId = options?.nodeId ?? 0; // this node's own id is 0
|
|
3175
|
+
const stagedRemoval = { value: undefined, version: txnTime, nodeId: removalNodeId };
|
|
3176
|
+
// re-delivered onto the removal it already made: writing again would append an audit entry
|
|
3177
|
+
// that every peer forwards and re-logs in turn. On a recommit the kept write batch shows this write's own
|
|
3178
|
+
// round-one tombstone as the existing entry, so a retry round repeats the first round's decision.
|
|
3179
|
+
const redelivered = retry
|
|
3180
|
+
? write.redelivered
|
|
3181
|
+
: existingRecord == null && isSameRemoval(removalBefore(write, existingEntry), txnTime, removalNodeId);
|
|
3182
|
+
write.redelivered = redelivered;
|
|
3183
|
+
if (redelivered) {
|
|
3184
|
+
write.stagedEntry = stagedRemoval;
|
|
3185
|
+
write.skipped = true;
|
|
3186
|
+
return;
|
|
3187
|
+
}
|
|
3156
3188
|
updateIndices(id, existingRecord, null, transaction && { transaction });
|
|
3157
3189
|
if (audit || trackDeletes) {
|
|
3158
3190
|
updateRecord(
|
|
@@ -3176,7 +3208,7 @@ export function makeTable(options) {
|
|
|
3176
3208
|
// Only RocksDB's remove() takes an options object; on LMDB the 2nd arg is ifVersion, and its writes already join the batched txn.
|
|
3177
3209
|
removeEntry(primaryStore, existingEntry, isRocksDB && transaction ? { transaction } : undefined);
|
|
3178
3210
|
}
|
|
3179
|
-
write.stagedEntry =
|
|
3211
|
+
write.stagedEntry = stagedRemoval; // the key holds no record for the rest of this transaction
|
|
3180
3212
|
// the removal supersedes the nearest record an earlier write in this transaction stored
|
|
3181
3213
|
// (older ones were already marked by their staged successors), so its saved blobs are
|
|
3182
3214
|
// cleaned up post-commit unless its audit entry references them
|
|
@@ -4896,82 +4928,174 @@ export function makeTable(options) {
|
|
|
4896
4928
|
const exactCount = options?.exactCount;
|
|
4897
4929
|
const TIME_LIMIT = options?.timeLimit ?? 1000 / 2; // one second time limit, enforced by seeing if we are halfway through at 500ms
|
|
4898
4930
|
const start = performance.now();
|
|
4899
|
-
// `entryCount` (the exact key count) is only needed once the scan blows the time budget --
|
|
4900
|
-
// to decide whether to estimate and as the extrapolation base. On RocksDB it is a full
|
|
4901
|
-
// key-only scan, so we defer it: tables that finish within budget (the common case) and
|
|
4902
|
-
// `exact_count` requests never pay for it. `halfway`/`entryCount` stay 0 until first computed.
|
|
4903
4931
|
let entryCount = 0;
|
|
4904
|
-
let
|
|
4905
|
-
let
|
|
4906
|
-
|
|
4932
|
+
let remainderPhysical = 0;
|
|
4933
|
+
let estimator;
|
|
4934
|
+
// feature-detected per DESIGN.md's invariant for this API family; LMDB stores do not implement it
|
|
4935
|
+
const canEstimate =
|
|
4936
|
+
typeof primaryStore.createCountEstimator === 'function' && typeof primaryStore.estimateCount === 'function';
|
|
4937
|
+
let estimatorFailed = false;
|
|
4938
|
+
let warnedNoBase = false;
|
|
4939
|
+
let checkpoints = 0;
|
|
4940
|
+
let checkpointedEntries = 0;
|
|
4907
4941
|
let recordCount = 0;
|
|
4908
4942
|
let entriesScanned = 0;
|
|
4943
|
+
let lastKey;
|
|
4909
4944
|
let limit: number;
|
|
4910
|
-
|
|
4945
|
+
let nextCheckAt = start + TIME_LIMIT;
|
|
4946
|
+
for (const { key, value } of primaryStore.getRange({ start: true, lazy: true, snapshot: false })) {
|
|
4911
4947
|
if (value != null) recordCount++;
|
|
4912
4948
|
entriesScanned++;
|
|
4949
|
+
lastKey = key;
|
|
4913
4950
|
await rest();
|
|
4914
|
-
|
|
4915
|
-
|
|
4916
|
-
|
|
4917
|
-
|
|
4918
|
-
|
|
4919
|
-
|
|
4920
|
-
|
|
4951
|
+
// a table too small to reach the floor is small enough to finish exactly
|
|
4952
|
+
if (exactCount || entriesScanned < MIN_ESTIMATOR_SAMPLE) continue;
|
|
4953
|
+
const now = performance.now();
|
|
4954
|
+
if (now <= nextCheckAt) continue;
|
|
4955
|
+
nextCheckAt = now + TIME_LIMIT;
|
|
4956
|
+
checkpoints++;
|
|
4957
|
+
if (canEstimate && !estimatorFailed) {
|
|
4958
|
+
try {
|
|
4959
|
+
estimator ??= primaryStore.createCountEstimator({ start: true });
|
|
4960
|
+
estimator.advance(lastKey, entriesScanned - checkpointedEntries);
|
|
4961
|
+
checkpointedEntries = entriesScanned;
|
|
4962
|
+
entryCount = usableCount(estimator.estimate());
|
|
4963
|
+
} catch (error) {
|
|
4964
|
+
// a store closing concurrently -- drop_table can, while this scan is parked in a yield
|
|
4965
|
+
logger.debug?.('Count estimator unavailable, falling back to an exact scan', error);
|
|
4966
|
+
estimatorFailed = true;
|
|
4967
|
+
estimator = undefined;
|
|
4968
|
+
entryCount = 0;
|
|
4921
4969
|
}
|
|
4922
|
-
|
|
4923
|
-
|
|
4924
|
-
|
|
4925
|
-
|
|
4970
|
+
} else if (!canEstimate) {
|
|
4971
|
+
// `canEstimate` false is not the same as "LMDB": a RocksDB store whose native module predates
|
|
4972
|
+
// the estimator API lands here too, and `RocksDatabase.getStats()` carries no `entryCount`.
|
|
4973
|
+
try {
|
|
4974
|
+
const stats = primaryStore.getStats?.();
|
|
4975
|
+
entryCount = Number.isFinite(stats?.entryCount) && stats.entryCount > 0 ? stats.entryCount : 0;
|
|
4976
|
+
} catch {
|
|
4977
|
+
entryCount = 0;
|
|
4978
|
+
}
|
|
4979
|
+
}
|
|
4980
|
+
if (!entryCount && canEstimate && !estimatorFailed) {
|
|
4981
|
+
// Range estimates are block-granular and can report 0 for a store whose entries are still
|
|
4982
|
+
// in the memtable. Without a base the escape cannot fire at all, so fall back to the
|
|
4983
|
+
// whole-store property rather than silently walking the table.
|
|
4984
|
+
try {
|
|
4985
|
+
const wholeStore = primaryStore.getEstimatedKeyCount();
|
|
4986
|
+
entryCount = Number.isFinite(wholeStore) && wholeStore > 0 ? wholeStore : 0;
|
|
4987
|
+
} catch {
|
|
4988
|
+
entryCount = 0;
|
|
4989
|
+
}
|
|
4990
|
+
if (!entryCount && !warnedNoBase) {
|
|
4991
|
+
warnedNoBase = true;
|
|
4992
|
+
logger.debug?.(`No usable key-count estimate for ${tableName}; counting records by full scan`);
|
|
4993
|
+
}
|
|
4994
|
+
}
|
|
4995
|
+
// Zero is "no usable base": degrade to the exact scan. The checkpoint ceiling is what stops a
|
|
4996
|
+
// base that keeps undershooting from holding the halfway test false forever and walking the
|
|
4997
|
+
// whole table; the reverse sample is bounded by `limit` in turn.
|
|
4998
|
+
if (
|
|
4999
|
+
entryCount > 0 &&
|
|
5000
|
+
(checkpoints >= MAX_ESTIMATE_CHECKPOINTS || entriesScanned < Math.floor(entryCount / 2))
|
|
5001
|
+
) {
|
|
5002
|
+
if (canEstimate) {
|
|
5003
|
+
try {
|
|
5004
|
+
const remaining = primaryStore.estimateCount({ start: lastKey, exclusiveStart: true });
|
|
5005
|
+
// widened by its own reported untrustworthiness: block-granular, so it can land below
|
|
5006
|
+
// the live count it is meant to bound
|
|
5007
|
+
const remainingCount = usableCount(remaining);
|
|
5008
|
+
remainderPhysical = remainingCount > 0 ? remainingCount * (2 - remaining.confidence) : 0;
|
|
5009
|
+
} catch {
|
|
5010
|
+
remainderPhysical = 0;
|
|
5011
|
+
}
|
|
5012
|
+
// A zero or unusable remainder is valid -- entries still in the memtable read as none
|
|
5013
|
+
// through range statistics -- but it would leave `baseMax` resting on the sampled ends
|
|
5014
|
+
// alone. The whole-store property is a separate, non-range source, so fall back to it.
|
|
5015
|
+
if (!remainderPhysical) {
|
|
5016
|
+
try {
|
|
5017
|
+
const wholeStore = primaryStore.getEstimatedKeyCount();
|
|
5018
|
+
if (Number.isFinite(wholeStore)) remainderPhysical = Math.max(wholeStore - entriesScanned, 0);
|
|
5019
|
+
} catch {
|
|
5020
|
+
remainderPhysical = 0;
|
|
5021
|
+
}
|
|
5022
|
+
}
|
|
4926
5023
|
}
|
|
4927
|
-
|
|
4928
|
-
|
|
4929
|
-
completeForExact = true;
|
|
5024
|
+
limit = entriesScanned;
|
|
5025
|
+
break;
|
|
4930
5026
|
}
|
|
4931
5027
|
}
|
|
4932
5028
|
if (limit) {
|
|
4933
5029
|
// in this case we are going to make an estimate of the table count using the first thousand
|
|
4934
5030
|
// entries and last thousand entries
|
|
4935
5031
|
const firstRecordCount = recordCount;
|
|
5032
|
+
const firstKey = lastKey;
|
|
4936
5033
|
recordCount = 0;
|
|
4937
5034
|
// Bound the reverse scan explicitly. The getRange `limit` option is honored by lmdb-js but
|
|
4938
5035
|
// ignored by rocksdb-js; without this break the scan reads the whole table, so `recordRate`
|
|
4939
5036
|
// blows up to ~entryCount/(2*limit) and the estimate scales with entryCount^2 -- the source
|
|
4940
5037
|
// of the wildly inflated `record_count` (e.g. 20,000,000 for ~105k rows) on large RocksDB
|
|
4941
|
-
// tables.
|
|
5038
|
+
// tables.
|
|
4942
5039
|
let reverseScanned = 0;
|
|
4943
|
-
|
|
5040
|
+
// Sized independently of the forward scan. `entriesScanned` is whatever the forward pass
|
|
5041
|
+
// covered before it escaped, and the checkpoint ceiling lets that run twenty budget
|
|
5042
|
+
// intervals when the base keeps undershooting; matching it here would read that same count
|
|
5043
|
+
// again and double the wall clock of the call this path exists to bound.
|
|
5044
|
+
const reverseLimit = Math.min(limit, MIN_ESTIMATOR_SAMPLE);
|
|
5045
|
+
// Disjointness is enforced against the forward scan's own last key rather than inferred from
|
|
5046
|
+
// the base, which is an estimate that can overshoot by more than 2x.
|
|
5047
|
+
let sampledWholeTable = false;
|
|
5048
|
+
for (const { key, value } of primaryStore.getRange({
|
|
4944
5049
|
start: '\uffff',
|
|
4945
5050
|
reverse: true,
|
|
4946
5051
|
lazy: true,
|
|
4947
|
-
limit,
|
|
5052
|
+
limit: reverseLimit,
|
|
4948
5053
|
snapshot: false,
|
|
4949
5054
|
})) {
|
|
5055
|
+
if (compareKeys(key, firstKey) <= 0) {
|
|
5056
|
+
sampledWholeTable = true;
|
|
5057
|
+
break;
|
|
5058
|
+
}
|
|
4950
5059
|
if (value != null) recordCount++;
|
|
4951
5060
|
reverseScanned++;
|
|
4952
5061
|
await rest();
|
|
4953
|
-
if (reverseScanned >=
|
|
5062
|
+
if (reverseScanned >= reverseLimit) break;
|
|
4954
5063
|
}
|
|
5064
|
+
// the samples met, so between them they covered every entry
|
|
5065
|
+
if (sampledWholeTable) return { recordCount: recordCount + firstRecordCount };
|
|
4955
5066
|
// Use the actual entries sampled, not limit*2: the reverse scan can yield fewer than `limit`
|
|
4956
5067
|
// (concurrent deletions under snapshot:false, or an overestimated entryCount), and counting
|
|
4957
5068
|
// those un-scanned slots would inflate the denominator and underestimate the rate.
|
|
4958
|
-
const
|
|
4959
|
-
const recordRate = (
|
|
4960
|
-
|
|
4961
|
-
|
|
4962
|
-
|
|
4963
|
-
|
|
4964
|
-
|
|
4965
|
-
//
|
|
4966
|
-
|
|
4967
|
-
const
|
|
4968
|
-
const
|
|
4969
|
-
|
|
4970
|
-
|
|
4971
|
-
|
|
5069
|
+
const sampledRecords = recordCount + firstRecordCount;
|
|
5070
|
+
const recordRate = sampledRecords / (limit + reverseScanned);
|
|
5071
|
+
// Endpoints for the extrapolation base, spanning both ways an estimated base can be wrong:
|
|
5072
|
+
// every remaining entry superseded (only what was sampled is live) through every remaining
|
|
5073
|
+
// entry live (the uncalibrated physical count). Churn concentrated outside the sampled ends
|
|
5074
|
+
// calibrates to nothing, so an interval derived from the estimator's confidence would sit
|
|
5075
|
+
// narrowly around the wrong number. Both endpoints are themselves estimates on RocksDB, so
|
|
5076
|
+
// this is a widened heuristic interval, not a guaranteed bound on the live count.
|
|
5077
|
+
const baseMin = entriesScanned + reverseScanned;
|
|
5078
|
+
const baseMax = Math.max(entriesScanned + remainderPhysical, entryCount, baseMin);
|
|
5079
|
+
const estimatedRecordCount = Math.round(recordRate * Math.max(entryCount, baseMin));
|
|
5080
|
+
// The samples counted these directly, and the entries between them can only add; everything
|
|
5081
|
+
// outside the samples could be live. A statistical interval inside those endpoints would be
|
|
5082
|
+
// narrowest exactly where the ends are least representative of the middle -- sampled ends
|
|
5083
|
+
// that are all deletion entries give a rate of 0, collapsing an upper end to ~0 with live
|
|
5084
|
+
// rows in between -- so the endpoints are the evidence itself.
|
|
5085
|
+
const lower = sampledRecords;
|
|
5086
|
+
const upper = Math.round(baseMax);
|
|
5087
|
+
// Report only the precision the interval supports, but never so coarse a unit that the
|
|
5088
|
+
// estimate rounds away: `baseMax` is physical and can exceed a calibrated estimate by
|
|
5089
|
+
// orders of magnitude, which a single division cannot walk back.
|
|
5090
|
+
let significantUnit = Math.pow(10, Math.round(Math.log10(Math.max((upper - lower) / 2, 1))));
|
|
5091
|
+
while (significantUnit > estimatedRecordCount && significantUnit > 1) significantUnit /= 10;
|
|
5092
|
+
recordCount = Math.min(
|
|
5093
|
+
Math.max(Math.round(estimatedRecordCount / significantUnit) * significantUnit, lower),
|
|
5094
|
+
upper
|
|
5095
|
+
);
|
|
4972
5096
|
return {
|
|
4973
5097
|
recordCount,
|
|
4974
|
-
estimatedRange: [
|
|
5098
|
+
estimatedRange: [lower, upper],
|
|
4975
5099
|
};
|
|
4976
5100
|
}
|
|
4977
5101
|
return {
|
|
@@ -5857,6 +5981,23 @@ export function makeTable(options) {
|
|
|
5857
5981
|
return ids;
|
|
5858
5982
|
}
|
|
5859
5983
|
|
|
5984
|
+
/**
|
|
5985
|
+
* What left a delete's key without a record: the nearest earlier write in the transaction that staged
|
|
5986
|
+
* state, else the stored entry. Only writes marked skipped are passed over, because an invalidate or
|
|
5987
|
+
* relocate stores a null stub without staging it.
|
|
5988
|
+
*/
|
|
5989
|
+
function removalBefore(write: any, existingEntry: Entry | undefined): Partial<Entry> | undefined {
|
|
5990
|
+
for (let prior = write.priorWrite; prior; prior = prior.priorWrite) {
|
|
5991
|
+
if (prior.stagedEntry) return prior.stagedEntry;
|
|
5992
|
+
if (!prior.skipped) return;
|
|
5993
|
+
}
|
|
5994
|
+
if (!(existingEntry?.metadataFlags & INVALIDATED)) return existingEntry;
|
|
5995
|
+
}
|
|
5996
|
+
|
|
5997
|
+
function isSameRemoval(entry: Partial<Entry> | undefined, txnTime: number, nodeId: number): boolean {
|
|
5998
|
+
return entry != null && entry.value == null && entry.version === txnTime && (entry.nodeId ?? 0) === nodeId;
|
|
5999
|
+
}
|
|
6000
|
+
|
|
5860
6001
|
function precedesExistingVersion(txnTime: number, existingEntry: Partial<Entry>, nodeId?: number): number {
|
|
5861
6002
|
if (nodeId === undefined) {
|
|
5862
6003
|
nodeId = getThisNodeId(auditStore);
|
|
@@ -6089,25 +6230,34 @@ export function makeTable(options) {
|
|
|
6089
6230
|
}
|
|
6090
6231
|
resolve(resolvedEntry);
|
|
6091
6232
|
} catch (error) {
|
|
6092
|
-
|
|
6093
|
-
|
|
6094
|
-
|
|
6095
|
-
|
|
6096
|
-
|
|
6097
|
-
|
|
6098
|
-
|
|
6099
|
-
|
|
6100
|
-
|
|
6101
|
-
|
|
6102
|
-
|
|
6103
|
-
|
|
6104
|
-
|
|
6105
|
-
|
|
6106
|
-
|
|
6107
|
-
|
|
6108
|
-
|
|
6109
|
-
|
|
6110
|
-
|
|
6233
|
+
// A source may reject with anything at all, so deciding how to settle is itself
|
|
6234
|
+
// fallible: `message` is not assignable on every error (a DOMException from
|
|
6235
|
+
// AbortSignal.timeout), and a nullish rejection makes the reads below throw.
|
|
6236
|
+
// Leaving this promise unsettled hangs the caller forever, so every path here
|
|
6237
|
+
// has to end in resolve() or reject().
|
|
6238
|
+
try {
|
|
6239
|
+
appendErrorContext(error, ` while resolving record ${id} for ${tableName}`);
|
|
6240
|
+
if (
|
|
6241
|
+
existingRecord &&
|
|
6242
|
+
(((error.code === 'ECONNRESET' || error.code === 'ECONNREFUSED' || error.code === 'EAI_AGAIN') &&
|
|
6243
|
+
!context?.mustRevalidate) ||
|
|
6244
|
+
(context?.staleIfError &&
|
|
6245
|
+
(error.statusCode === 500 ||
|
|
6246
|
+
error.statusCode === 502 ||
|
|
6247
|
+
error.statusCode === 503 ||
|
|
6248
|
+
error.statusCode === 504)))
|
|
6249
|
+
) {
|
|
6250
|
+
// these are conditions under which we can use stale data after an error
|
|
6251
|
+
resolve({
|
|
6252
|
+
key: id,
|
|
6253
|
+
version: existingVersion,
|
|
6254
|
+
value: existingRecord,
|
|
6255
|
+
} as any);
|
|
6256
|
+
logger.trace?.((error as Error)?.message, '(returned stale record)');
|
|
6257
|
+
} else reject(error);
|
|
6258
|
+
} catch (settlingError) {
|
|
6259
|
+
reject(error ?? settlingError);
|
|
6260
|
+
}
|
|
6111
6261
|
const resolveDuration = performance.now() - start;
|
|
6112
6262
|
recordAction(resolveDuration, 'cache-resolution', tableName, null, 'fail');
|
|
6113
6263
|
if (responseHeaders)
|
package/resources/replayLogs.ts
CHANGED
|
@@ -75,8 +75,9 @@ export function replayLogs(rootStore: RocksDatabase, tables: any): Promise<void>
|
|
|
75
75
|
tableById.set(table.tableId, table);
|
|
76
76
|
}
|
|
77
77
|
// replay all the logs
|
|
78
|
-
let transaction: DatabaseTransaction;
|
|
78
|
+
let transaction: DatabaseTransaction | undefined;
|
|
79
79
|
let lastTimestamp = 0;
|
|
80
|
+
let openCommitEnded = false;
|
|
80
81
|
let writes = 0;
|
|
81
82
|
let skipped = 0;
|
|
82
83
|
// Track forward progress so a backlog of unwritable entries can't grind the boot thread
|
|
@@ -110,8 +111,27 @@ export function replayLogs(rootStore: RocksDatabase, tables: any): Promise<void>
|
|
|
110
111
|
originatingOperation,
|
|
111
112
|
username,
|
|
112
113
|
extendedType,
|
|
114
|
+
endTxn,
|
|
113
115
|
} = auditRecord;
|
|
114
116
|
try {
|
|
117
|
+
// A replay transaction is one native commit ("Boot replay transactions" in DESIGN.md).
|
|
118
|
+
if (transaction && (openCommitEnded || lastTimestamp !== version)) {
|
|
119
|
+
const ending = transaction;
|
|
120
|
+
transaction = undefined;
|
|
121
|
+
try {
|
|
122
|
+
ending.directCommitSync();
|
|
123
|
+
} catch (error) {
|
|
124
|
+
logger.error('Error committing replay transaction', error);
|
|
125
|
+
}
|
|
126
|
+
// The wall-clock budget (harper#1316) is checked only here, between native commits with
|
|
127
|
+
// nothing staged, so aborting never tears a commit in half.
|
|
128
|
+
if (shouldAbortSlowReplay(performance.now() - replayStartTime, replayTimeoutMs)) {
|
|
129
|
+
logger.fatal(
|
|
130
|
+
`Aborting transaction-log replay in ${(rootStore as any).databaseName} database: replay has exceeded the wall-clock time limit (${writes} written, ${skipped} skipped). The transaction log contains a pathologically deep out-of-order write history that is too expensive to reconcile during boot (harper#1316). Re-clone this node from a healthy leader to recover the unreplayed data.`
|
|
131
|
+
);
|
|
132
|
+
break;
|
|
133
|
+
}
|
|
134
|
+
}
|
|
115
135
|
if (classifyAuditEntryForReplay(extendedType, tableId, true) === 'corrupt-header') {
|
|
116
136
|
skipped++;
|
|
117
137
|
noProgressRun++;
|
|
@@ -165,28 +185,9 @@ export function replayLogs(rootStore: RocksDatabase, tables: any): Promise<void>
|
|
|
165
185
|
warnedReplayHappening = true;
|
|
166
186
|
console.warn('Harper was not properly shutdown, replaying transaction logs to synchronize database');
|
|
167
187
|
}
|
|
168
|
-
if (
|
|
188
|
+
if (!transaction) {
|
|
169
189
|
lastTimestamp = version;
|
|
170
|
-
|
|
171
|
-
// commit the last transaction since we are starting a new one
|
|
172
|
-
transaction?.directCommitSync();
|
|
173
|
-
} catch (error) {
|
|
174
|
-
logger.error('Error committing replay transaction', error);
|
|
175
|
-
}
|
|
176
|
-
// Abort if replay has exceeded the total wall-clock budget even while making progress
|
|
177
|
-
// (harper#1316, facet a). shouldAbortStalledReplay resets its counters on every write,
|
|
178
|
-
// so a slow-but-progressing replay (deep out-of-order audit chain walk per entry) can
|
|
179
|
-
// peg the boot thread indefinitely without tripping it. Checked only here, at a version
|
|
180
|
-
// boundary: the prior version's transaction was just committed in full and the new one
|
|
181
|
-
// is not yet staged, so aborting never tears a same-version (same source-transaction)
|
|
182
|
-
// write batch in half. Re-clone to recover the unreplayed remainder.
|
|
183
|
-
if (shouldAbortSlowReplay(performance.now() - replayStartTime, replayTimeoutMs)) {
|
|
184
|
-
logger.fatal(
|
|
185
|
-
`Aborting transaction-log replay in ${(rootStore as any).databaseName} database: replay has exceeded the wall-clock time limit (${writes} written, ${skipped} skipped). The transaction log contains a pathologically deep out-of-order write history that is too expensive to reconcile during boot (harper#1316). Re-clone this node from a healthy leader to recover the unreplayed data.`
|
|
186
|
-
);
|
|
187
|
-
transaction = undefined as any; // already committed above; nothing staged for the new version
|
|
188
|
-
break;
|
|
189
|
-
}
|
|
190
|
+
openCommitEnded = false;
|
|
190
191
|
transaction = new DatabaseTransaction();
|
|
191
192
|
transaction.db = primaryStore;
|
|
192
193
|
transaction.timestamp = version;
|
|
@@ -281,6 +282,8 @@ export function replayLogs(rootStore: RocksDatabase, tables: any): Promise<void>
|
|
|
281
282
|
logger.error(`Error writing from replay of log`, err, {
|
|
282
283
|
version,
|
|
283
284
|
});
|
|
285
|
+
} finally {
|
|
286
|
+
if (endTxn) openCommitEnded = true;
|
|
284
287
|
}
|
|
285
288
|
}
|
|
286
289
|
try {
|
|
@@ -192,7 +192,7 @@ function notifyFromTransactionData(subscriptions, auditLogIterable?, allowYield
|
|
|
192
192
|
let ancestorLevel = 0;
|
|
193
193
|
do {
|
|
194
194
|
// we iterate through the key hierarchy, notifying all subscribers for each key,
|
|
195
|
-
// so for an id like resource/foo/bar, we notify subscribers for resource/foo/bar, resource/foo/, resource
|
|
195
|
+
// so for an id like resource/foo/bar, we notify subscribers for resource/foo/bar, resource/foo/, resource/, and the root (null)
|
|
196
196
|
// this allows for efficient subscriptions to children ids/topics
|
|
197
197
|
const keySubscriptions = tableSubscriptions.get(matchingKey);
|
|
198
198
|
if (keySubscriptions) {
|
|
@@ -230,9 +230,9 @@ function notifyFromTransactionData(subscriptions, auditLogIterable?, allowYield
|
|
|
230
230
|
if (lastSlash !== matchingKey.length - 1) {
|
|
231
231
|
ancestorLevel++; // don't increase the ancestor level for this going from resource/ to resource
|
|
232
232
|
}
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
233
|
+
// lastIndexOf clamps a negative fromIndex to 0, so '/' would otherwise yield itself forever
|
|
234
|
+
const parentKey = lastSlash > -1 ? matchingKey.slice(0, lastSlash + 1) : null;
|
|
235
|
+
matchingKey = parentKey === matchingKey ? null : parentKey;
|
|
236
236
|
} while (true);
|
|
237
237
|
}
|
|
238
238
|
} else if (auditRecord.type === 'reload') {
|
|
@@ -27,10 +27,14 @@ import { extractCertificateChain, extractRevocationUrls, bufferToPem } from './v
|
|
|
27
27
|
import { getCachedCertificateVerificationConfig } from './verificationConfig.ts';
|
|
28
28
|
import { verifyOCSP } from './ocspVerification.ts';
|
|
29
29
|
import { verifyCRL } from './crlVerification.ts';
|
|
30
|
-
import
|
|
30
|
+
import { resolveTrustedIssuer } from './trustedIssuers.ts';
|
|
31
|
+
import type { PeerCertificate, CertificateVerificationResult, FailureMode } from './types.ts';
|
|
31
32
|
|
|
32
33
|
const logger = loggerWithTag('cert-verification');
|
|
33
34
|
|
|
35
|
+
const warnedUnresolvedLeaves = new Set<string>();
|
|
36
|
+
const MAX_WARNED_LEAVES = 10_000;
|
|
37
|
+
|
|
34
38
|
/**
|
|
35
39
|
* Verify certificate revocation status using OCSP and/or CRL
|
|
36
40
|
* @param peerCertificate - Peer certificate object from TLS connection
|
|
@@ -52,14 +56,32 @@ export async function verifyCertificate(
|
|
|
52
56
|
return { valid: true, status: 'disabled', method: 'disabled' };
|
|
53
57
|
}
|
|
54
58
|
|
|
59
|
+
// ahead of the chain check so an explicitly disabled control can never reject
|
|
60
|
+
if (config.crl.enabled === false && config.ocsp.enabled === false) {
|
|
61
|
+
logger.debug?.('Both CRL and OCSP disabled - verification disabled');
|
|
62
|
+
return { valid: true, status: 'disabled', method: 'disabled' };
|
|
63
|
+
}
|
|
64
|
+
|
|
55
65
|
// Extract certificate chain
|
|
56
66
|
const certChain = extractCertificateChain(peerCertificate);
|
|
57
67
|
logger.trace?.(`Certificate chain length: ${certChain.length}`);
|
|
58
68
|
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
69
|
+
if (certChain.length === 0) {
|
|
70
|
+
return unresolvedIssuerResult(config.failureMode, peerCertificate, 'no certificate data');
|
|
71
|
+
}
|
|
72
|
+
// Revocation checking needs the issuer. The socket omits it on every resumed TLS session and on
|
|
73
|
+
// Node 26.8.0/26.8.1 (nodejs/node#65579); recover it from the CA set that authorized the client.
|
|
74
|
+
if (!certChain[0].issuer) {
|
|
75
|
+
const issuer = resolveTrustedIssuer(certChain[0].cert, peerCertificate.fingerprint256);
|
|
76
|
+
if (!issuer) {
|
|
77
|
+
return unresolvedIssuerResult(
|
|
78
|
+
config.failureMode,
|
|
79
|
+
peerCertificate,
|
|
80
|
+
'issuer is neither in the presented chain nor among the configured certificate authorities'
|
|
81
|
+
);
|
|
82
|
+
}
|
|
83
|
+
logger.debug?.('Issuer certificate resolved from the configured certificate authorities');
|
|
84
|
+
certChain[0].issuer = issuer;
|
|
63
85
|
}
|
|
64
86
|
|
|
65
87
|
// Extract certificate revocation URLs in single parse operation
|
|
@@ -117,17 +139,36 @@ export async function verifyCertificate(
|
|
|
117
139
|
logger.debug?.('Skipping OCSP - no responder URLs in certificate');
|
|
118
140
|
}
|
|
119
141
|
|
|
120
|
-
// All methods tried or skipped -
|
|
121
|
-
// If both methods are explicitly disabled (not just unavailable), treat as disabled
|
|
122
|
-
if (config.crl.enabled === false && config.ocsp.enabled === false) {
|
|
123
|
-
logger.debug?.('Both CRL and OCSP disabled - verification disabled');
|
|
124
|
-
return { valid: true, status: 'disabled', method: 'disabled' };
|
|
125
|
-
}
|
|
126
|
-
|
|
127
|
-
// Methods are enabled but unavailable - apply failure mode
|
|
142
|
+
// All methods tried or skipped - apply failure mode
|
|
128
143
|
if (config.failureMode === 'fail-closed') {
|
|
129
144
|
return { valid: false, status: 'no-verification-available', method: 'disabled' };
|
|
130
145
|
}
|
|
131
146
|
|
|
132
147
|
return { valid: true, status: 'verification-unavailable-allowed', method: 'disabled' };
|
|
133
148
|
}
|
|
149
|
+
|
|
150
|
+
/**
|
|
151
|
+
* Revocation status cannot be established without the issuer, so the configured failure mode
|
|
152
|
+
* decides: refuse under fail-closed, allow under fail-open. Either way this is a security control
|
|
153
|
+
* not running, so it is logged at warn (once per certificate) rather than debug.
|
|
154
|
+
*/
|
|
155
|
+
function unresolvedIssuerResult(
|
|
156
|
+
failureMode: FailureMode | undefined,
|
|
157
|
+
peerCertificate: PeerCertificate,
|
|
158
|
+
reason: string
|
|
159
|
+
): CertificateVerificationResult {
|
|
160
|
+
const valid = failureMode !== 'fail-closed';
|
|
161
|
+
const leafKey = peerCertificate.fingerprint256 ?? peerCertificate.subject?.CN ?? '';
|
|
162
|
+
if (!warnedUnresolvedLeaves.has(leafKey)) {
|
|
163
|
+
if (warnedUnresolvedLeaves.size >= MAX_WARNED_LEAVES) warnedUnresolvedLeaves.clear();
|
|
164
|
+
warnedUnresolvedLeaves.add(leafKey);
|
|
165
|
+
logger.warn?.(
|
|
166
|
+
`Cannot check revocation status for client certificate ${peerCertificate.subject?.CN ?? 'unknown'}: ${reason}; ${
|
|
167
|
+
valid ? 'allowing connection (fail-open)' : 'rejecting connection (fail-closed)'
|
|
168
|
+
}`
|
|
169
|
+
);
|
|
170
|
+
} else {
|
|
171
|
+
logger.debug?.(`Cannot check revocation status for ${peerCertificate.subject?.CN ?? 'unknown'}: ${reason}`);
|
|
172
|
+
}
|
|
173
|
+
return { valid, status: 'no-issuer-cert', method: 'disabled' };
|
|
174
|
+
}
|