@harperfast/harper 5.3.2 → 5.3.3
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/cliOperations.ts +3 -3
- package/bin/copyDb.ts +4 -3
- package/components/DESIGN.md +12 -0
- package/components/packageComponent.ts +42 -17
- package/config/DESIGN.md +4 -0
- package/config/configUtils.ts +8 -8
- package/config/parseConfigFile.ts +11 -7
- package/dist/bin/cliOperations.js +3 -3
- package/dist/bin/cliOperations.js.map +1 -1
- package/dist/bin/copyDb.js +3 -3
- package/dist/bin/copyDb.js.map +1 -1
- package/dist/components/packageComponent.d.ts +9 -7
- package/dist/components/packageComponent.js +41 -16
- package/dist/components/packageComponent.js.map +1 -1
- package/dist/config/configUtils.js +4 -3
- package/dist/config/configUtils.js.map +1 -1
- package/dist/config/parseConfigFile.d.ts +1 -0
- package/dist/config/parseConfigFile.js +7 -3
- package/dist/config/parseConfigFile.js.map +1 -1
- package/dist/resources/DatabaseTransaction.d.ts +75 -10
- package/dist/resources/DatabaseTransaction.js +1080 -277
- package/dist/resources/DatabaseTransaction.js.map +1 -1
- package/dist/resources/Resource.js +40 -14
- package/dist/resources/Resource.js.map +1 -1
- package/dist/resources/RocksTransactionLogStore.js +1 -0
- package/dist/resources/RocksTransactionLogStore.js.map +1 -1
- package/dist/resources/Table.js +16 -2
- package/dist/resources/Table.js.map +1 -1
- package/dist/resources/analytics/write.js +34 -16
- package/dist/resources/analytics/write.js.map +1 -1
- package/dist/resources/databases.d.ts +5 -0
- package/dist/resources/databases.js +38 -4
- package/dist/resources/databases.js.map +1 -1
- package/dist/resources/recordLock.d.ts +9 -4
- package/dist/resources/recordLock.js +79 -19
- package/dist/resources/recordLock.js.map +1 -1
- package/dist/server/DurableSubscriptionsSession.d.ts +2 -0
- package/dist/server/DurableSubscriptionsSession.js +20 -17
- package/dist/server/DurableSubscriptionsSession.js.map +1 -1
- package/dist/server/mqtt.js +41 -23
- package/dist/server/mqtt.js.map +1 -1
- package/dist/server/serverHelpers/NodeAdapterResponse.js +18 -0
- package/dist/server/serverHelpers/NodeAdapterResponse.js.map +1 -1
- package/dist/utility/npmUtilities.js +18 -7
- package/dist/utility/npmUtilities.js.map +1 -1
- package/package.json +1 -1
- package/resources/DESIGN.md +42 -10
- package/resources/DatabaseTransaction.ts +1244 -275
- package/resources/Resource.ts +39 -14
- package/resources/RocksTransactionLogStore.ts +1 -0
- package/resources/Table.ts +24 -2
- package/resources/analytics/DESIGN.md +17 -0
- package/resources/analytics/write.ts +28 -15
- package/resources/databases.ts +40 -4
- package/resources/record-locks.md +34 -17
- package/resources/recordLock.ts +89 -22
- package/server/DESIGN.md +2 -0
- package/server/DurableSubscriptionsSession.ts +18 -16
- package/server/mqtt.ts +39 -22
- package/server/serverHelpers/NodeAdapterResponse.ts +19 -1
- package/studio/web/assets/Chat-B-Rd-ft9.js +2100 -0
- package/studio/web/assets/FloatingChat-CFypO7kh.js +23 -0
- package/studio/web/assets/SearchDialog-BRIyPiBl.js +1 -0
- package/studio/web/assets/{abnfDiagram-O67JEVCF-raXE027J.js → abnfDiagram-YUKMZFIV-DPR2lFRk.js} +1 -1
- package/studio/web/assets/{apiToken-DNrfij0E.js → apiToken-BraYnzvC.js} +1 -1
- package/studio/web/assets/applications-XCds8-ZP.js +296 -0
- package/studio/web/assets/architecture-WOLXFQ4H-DtJHIK0_.js +1 -0
- package/studio/web/assets/{architectureDiagram-NJMV4G6O-C8_Eet7w.js → architectureDiagram-47P4ROYG-MDymp2dh.js} +3 -3
- package/studio/web/assets/authStore-DE1HFvVP.js +3 -0
- package/studio/web/assets/{blockDiagram-BEXU5L5S-DR6S5-1D.js → blockDiagram-E7TT5TSB-O5qRgZmd.js} +3 -3
- package/studio/web/assets/button-C37w80Uk.js +21 -0
- package/studio/web/assets/{c4Diagram-YGBWAQC7-BAfZ1G01.js → c4Diagram-CRA5TL53-BdPORGHo.js} +2 -2
- package/studio/web/assets/channel-yCStoq0_.js +1 -0
- package/studio/web/assets/{chevron-up-CFxbMGv2.js → chevron-up-DozwrIz-.js} +1 -1
- package/studio/web/assets/chunk-2BW5OAIV-DJ-iXJAw.js +10 -0
- package/studio/web/assets/{chunk-ZIGJFQKS-Cs8fUn7y.js → chunk-3YJQHVM4-BOgacqS1.js} +2 -2
- package/studio/web/assets/{chunk-7PRAP22T-KyqX6x3f.js → chunk-4EA7E6EY-C-7xqUBl.js} +1 -1
- package/studio/web/assets/{chunk-GWA4HPMP-DsejB6gA.js → chunk-7TKQ45FW-C3XA1bIN.js} +1 -1
- package/studio/web/assets/{chunk-GTNCS2PH-DGHLXyow.js → chunk-AW2ZBBNX-BdvgD1UW.js} +3 -3
- package/studio/web/assets/{chunk-5DYCD2WN-BceBfIHT.js → chunk-DBDB3WZW-CKKxIc6F.js} +1 -1
- package/studio/web/assets/{chunk-MBY4JIJT-BwSGweQP.js → chunk-E2ZNV5FY-bVefNHvz.js} +3 -3
- package/studio/web/assets/chunk-EU5HNXII-BHv2kWp4.js +213 -0
- package/studio/web/assets/chunk-FVRAUYC3-BoL_NO6b.js +2 -0
- package/studio/web/assets/{chunk-742MDFTN-CIkt8ijt.js → chunk-HJ2JQQFS-N_98oMRS.js} +1 -1
- package/studio/web/assets/{chunk-NETBCI7D-C8UnOw_f.js → chunk-HTAEGDNF-BQ1fSjPs.js} +1 -1
- package/studio/web/assets/{chunk-WEXAMYUT-BAIhTwyh.js → chunk-KQW6MTUR-qP0QiQAg.js} +1 -1
- package/studio/web/assets/chunk-NGNAAXSQ-DqoPQk1a.js +136 -0
- package/studio/web/assets/{chunk-UA2S7LBM-BHpEdH8D.js → chunk-NTY3LDVX-CSBFjlJg.js} +1 -1
- package/studio/web/assets/chunk-VPRB5NB3-BVHOe9Q-.js +129 -0
- package/studio/web/assets/{chunk-7INBJB4K-BpeAXyOi.js → chunk-XC4XBNZT-46uQwtF4.js} +10 -10
- package/studio/web/assets/{classDiagram-v2-NBCMYWYE-DmDPkt2u.js → classDiagram-v2-K4WV4PDN-Da0ps1Ui.js} +3 -3
- package/studio/web/assets/cn-DpF1PxBT.js +1 -0
- package/studio/web/assets/{createLucideIcon-fO5dL7D5.js → createLucideIcon-DLWRoWN-.js} +1 -1
- package/studio/web/assets/{cssMode-DBucrQdT.js → cssMode-BY1w91l8.js} +1 -1
- package/studio/web/assets/cynefin-EF2NZ3EQ-jQ2eCUaU.js +1 -0
- package/studio/web/assets/cynefinDiagram-3GCD6N5R-TNi5iS0E.js +62 -0
- package/studio/web/assets/dagre-W4DXFKR2-BnLHmA44.js +4 -0
- package/studio/web/assets/{diagram-22UHCM2B-DJCvcaWP.js → diagram-2UJZ2QOL-r_jehhZR.js} +7 -7
- package/studio/web/assets/{diagram-3UASUU5V-Du6VKWln.js → diagram-OVF4WLC6-BSUXGVh8.js} +11 -11
- package/studio/web/assets/{diagram-CDSNMT55-l5Ga0u-W.js → diagram-PFPMY2P6-BrVedycw.js} +3 -3
- package/studio/web/assets/{diagram-MLGK6HIB-CNQY47LA.js → diagram-UMYRVEAY-CBIaakBB.js} +1 -1
- package/studio/web/assets/diagram-YEKJPTXX-DFmmDXCl.js +41 -0
- package/studio/web/assets/diagram-ZIFT7M5P-CQfOWxOi.js +3 -0
- package/studio/web/assets/dist-9lpzt2Hn.js +11 -0
- package/studio/web/assets/{ebnfDiagram-ZINNZB2B-5OnkTpfi.js → ebnfDiagram-VR2GEFS7-FXA3Xe6e.js} +1 -1
- package/studio/web/assets/{editor-CHpQCuBq.js → editor-D5mpVs1b.js} +1 -1
- package/studio/web/assets/elk-IJKZMXRS-CC4gfVRU.js +27 -0
- package/studio/web/assets/{erDiagram-OPXOYQCR-CQlCX84D.js → erDiagram-O2IAPWRE-C-BkDTPl.js} +2 -2
- package/studio/web/assets/errorText-8xV8WZBt.js +1 -0
- package/studio/web/assets/eventmodeling-K75KTNOO-D0dJ_PbR.js +1 -0
- package/studio/web/assets/flowDiagram-OXPTDLAJ-C1LEc_Hf.js +1 -0
- package/studio/web/assets/{ganttDiagram-FUAMR5RP-bloX3Voy.js → ganttDiagram-R7TSEDQI-85F44BIo.js} +4 -4
- package/studio/web/assets/{getAnalytics-C0FHjtdQ.js → getAnalytics-B1xpd4kG.js} +1 -1
- package/studio/web/assets/gitGraph-VSP46ZUC-BBmqrmjK.js +1 -0
- package/studio/web/assets/gitGraphDiagram-XJZIOB7I-CFY6iMwg.js +106 -0
- package/studio/web/assets/graphlib-DS17s2tU.js +1 -0
- package/studio/web/assets/{html-B1iA8q38.js → html-DiXKiahz.js} +1 -1
- package/studio/web/assets/{htmlMode-CrL3YO5Y.js → htmlMode-DitVJ707.js} +1 -1
- package/studio/web/assets/index-CDnn0-PN.css +1 -0
- package/studio/web/assets/index-D8kyF6dt.js +833 -0
- package/studio/web/assets/index.lazy-DgbjUxLo.js +1 -0
- package/studio/web/assets/info-OHQRW6UA-Bs2aEco7.js +1 -0
- package/studio/web/assets/{infoDiagram-VRGFBTTK-B0J1132p.js → infoDiagram-5W2HQ5XZ-d6yG1yfZ.js} +1 -1
- package/studio/web/assets/invalidateEntityQueries-BkWXiqjz.js +1 -0
- package/studio/web/assets/{ishikawaDiagram-OU5B5YK6-BNk0tuQx.js → ishikawaDiagram-K3B6WC7H-CSYz-WRA.js} +2 -2
- package/studio/web/assets/{javascript-CXgQMkoa.js → javascript-BBVXeMDm.js} +1 -1
- package/studio/web/assets/{journeyDiagram-ZHPQQLJL-Be79RMQM.js → journeyDiagram-COXZFDF6-BDj6Wqcj.js} +3 -3
- package/studio/web/assets/{jsonMode-DycqRKOv.js → jsonMode-D85_j2oD.js} +1 -1
- package/studio/web/assets/{kanban-definition-PNTS6WVX-4uuoQP9W.js → kanban-definition-P3RFRI5V-C2Bjfbp-.js} +6 -6
- package/studio/web/assets/{languageServices-HYHV1wDJ.js → languageServices-U3mWhpRL.js} +1 -1
- package/studio/web/assets/{lspLanguageFeatures-CH_6SYiA.js → lspLanguageFeatures-CoymyM3c.js} +1 -1
- package/studio/web/assets/{mermaid-parser.core-V1UYMeXV.js → mermaid-parser.core-Ch_T61iL.js} +6 -6
- package/studio/web/assets/mermaid.core-BOAgKzo5.js +44 -0
- package/studio/web/assets/{mindmap-definition-NLK3R4M7-C70Ctm0J.js → mindmap-definition-LZFPQGKD-DzOapzMM.js} +3 -3
- package/studio/web/assets/notifications-CPgEC6ZP.js +1 -0
- package/studio/web/assets/notifications-D_VqhHnx.js +1 -0
- package/studio/web/assets/operations-D8aaKYty.js +4 -0
- package/studio/web/assets/packet-JDAUHWVQ-CGHB_EIE.js +1 -0
- package/studio/web/assets/{pegDiagram-GJSIUBJH-d6z1cc7W.js → pegDiagram-BGZESJAR-ADzOIKrz.js} +1 -1
- package/studio/web/assets/pie-XZMESJXO-Bijz5Y2o.js +1 -0
- package/studio/web/assets/pieDiagram-CAPJLFHJ-VM7fvbwO.js +39 -0
- package/studio/web/assets/profile-DM5E6Xls.js +1 -0
- package/studio/web/assets/{quadrantDiagram-O4NWA36T-CIF5Qkou.js → quadrantDiagram-GDMTTRAM-Bmza84_j.js} +3 -3
- package/studio/web/assets/radar-ABXABTNO-IaC8pB63.js +1 -0
- package/studio/web/assets/railroad-I3PHUGI6-CCrSUqeq.js +1 -0
- package/studio/web/assets/railroad-abnf-LNEFI6M7-BhobVp-t.js +1 -0
- package/studio/web/assets/railroad-ebnf-DWYD2UWJ-OHhGUTEh.js +1 -0
- package/studio/web/assets/railroad-peg-JR7BVNR7-FupC3Jcn.js +1 -0
- package/studio/web/assets/{railroadDiagram-XR7U4H2S-y3Jv2U6N.js → railroadDiagram-SM67HX2B-CgFGOVI5.js} +1 -1
- package/studio/web/assets/regions-WFvXqryO.js +1 -0
- package/studio/web/assets/{register-DCN4QUii.js → register-B7DvfAGj.js} +3 -3
- package/studio/web/assets/request-ChoMERRk.js +2 -0
- package/studio/web/assets/{requirementDiagram-PLB6GJNP-ChINZEmu.js → requirementDiagram-X7JNWC4A-BHoQgkoT.js} +4 -4
- package/studio/web/assets/restartGate-D_e93lFE.js +1 -0
- package/studio/web/assets/restartUnconfirmed-BqcDZ__Y.js +1 -0
- package/studio/web/assets/sankeyDiagram-UM26HJRW-8tFu_oUe.js +40 -0
- package/studio/web/assets/sequenceDiagram-ZO4K6R2Y-jryb1rwT.js +169 -0
- package/studio/web/assets/setComponentFile-BHk22Rh6.js +1 -0
- package/studio/web/assets/setup-wNdpTrVH.js +2 -0
- package/studio/web/assets/{stateDiagram-v2-GCMORJYK-Bs8RIb9K.js → stateDiagram-v2-TBUQTH76-CnWE5yFn.js} +2 -2
- package/studio/web/assets/status-CYoonm9V.js +61 -0
- package/studio/web/assets/{swimlanes-2SLR337P-B52xrf6t.js → swimlanes-N4OXWK64-CBlFzDJn.js} +1 -1
- package/studio/web/assets/swimlanesDiagram-K3J5GTZL-DvFLtUPi.js +8 -0
- package/studio/web/assets/{timeline-definition-EJHVYXUP-DJSITpQX.js → timeline-definition-YOQAKGHF-CFWqHrqI.js} +4 -4
- package/studio/web/assets/{toggleHighContrast-CfAp6Sxb.js → toggleHighContrast-CKEm-P4K.js} +1 -1
- package/studio/web/assets/treeView-D4JQ5SDB-CpV9zuVb.js +1 -0
- package/studio/web/assets/treemap-SAJKECNS-Bc94Tqiq.js +1 -0
- package/studio/web/assets/{tsMode-HZR0bfq4.js → tsMode-Dv35qHmn.js} +1 -1
- package/studio/web/assets/{typescript-UMJSfZyF.js → typescript-Bly1EW8o.js} +1 -1
- package/studio/web/assets/{useEntityRestURL-B379CS-h.js → useEntityRestURL-Cg3P0MOb.js} +1 -1
- package/studio/web/assets/usecaseDiagram-VIAY4XPW-Brf2xP3S.js +328 -0
- package/studio/web/assets/{vendor-core-6LhfJlRC.js → vendor-core-PejxhUWQ.js} +1 -1
- package/studio/web/assets/vendor-datadog-GjET_Cbn.js +6 -0
- package/studio/web/assets/{vendor-react-BOliS0BL.js → vendor-react-DSIx92Ld.js} +1 -1
- package/studio/web/assets/vendor-tanstack-C5Y-nrd2.js +2 -0
- package/studio/web/assets/vendor-ui-BBWHOEoQ.js +41 -0
- package/studio/web/assets/{vennDiagram-UO4OBE2U-DZTsDA4k.js → vennDiagram-BLWOH2XV-XxOyJPq_.js} +3 -3
- package/studio/web/assets/wardley-7MLQ67FV-DRW0zuE4.js +1 -0
- package/studio/web/assets/{wardleyDiagram-VNRHLVJA-qu_f7Tvl.js → wardleyDiagram-YMQ3BMBF-B3WskJ9W.js} +3 -3
- package/studio/web/assets/wasAReleasedBeforeB-D5grAaGM.js +1 -0
- package/studio/web/assets/{workers-Bn_0_L1R.js → workers-DVtjrvUa.js} +1 -1
- package/studio/web/assets/{xml-CG2N-1r1.js → xml-DjAPbgLN.js} +1 -1
- package/studio/web/assets/xychartDiagram-TAQBALBS-D5NV4Env.js +7 -0
- package/studio/web/assets/{yaml-68pda6Av.js → yaml-oTUyoJy1.js} +1 -1
- package/studio/web/index.html +17 -15
- package/utility/npmUtilities.ts +17 -7
- package/studio/web/assets/Chat-CrxPD4ph.js +0 -2294
- package/studio/web/assets/FloatingChat-EXzoUQ-9.js +0 -23
- package/studio/web/assets/SearchDialog-7qRL8Yqx.js +0 -1
- package/studio/web/assets/_basePickBy-QJXUw_pr.js +0 -1
- package/studio/web/assets/_baseUniq-DR4vVjyC.js +0 -1
- package/studio/web/assets/alertDialog-BbWD9Xxa.js +0 -1
- package/studio/web/assets/applications-CdqjiSQe.js +0 -296
- package/studio/web/assets/architecture-7GRP2DOG-DDmz3hpI.js +0 -1
- package/studio/web/assets/arrow-up-D2g_57vT.js +0 -1
- package/studio/web/assets/authStore-CJCaADw9.js +0 -3
- package/studio/web/assets/button-DWwfgGWI.js +0 -21
- package/studio/web/assets/channel-D0AkPjJ8.js +0 -1
- package/studio/web/assets/chunk-3FUC2YCW-DuRp-j19.js +0 -2
- package/studio/web/assets/chunk-7M6MHVWA-h6WlaTzO.js +0 -213
- package/studio/web/assets/chunk-FOHPRMQF-DzxwRta7.js +0 -161
- package/studio/web/assets/chunk-O7XYJQB3-CBS3nZYV.js +0 -128
- package/studio/web/assets/chunk-Z7XXMR3K-DSHbImdf.js +0 -10
- package/studio/web/assets/cn-6T8VranJ.js +0 -1
- package/studio/web/assets/cynefin-OW5HDTMX-DIvbXLSj.js +0 -1
- package/studio/web/assets/cynefinDiagram-VND7K2PF-BdtaXiCE.js +0 -62
- package/studio/web/assets/dagre-6A5THRUB-C7RUWHyz.js +0 -4
- package/studio/web/assets/diagram-ATOU4E4O-dxRfeG4B.js +0 -3
- package/studio/web/assets/diagram-MPIPVDR6-7prveD1B.js +0 -41
- package/studio/web/assets/dist-BrRuMUzt.js +0 -11
- package/studio/web/assets/download-Cn6tPG4L.js +0 -1
- package/studio/web/assets/elk-276RUBZZ-DxDQPjQT.js +0 -27
- package/studio/web/assets/errorText-NpBmvk9I.js +0 -1
- package/studio/web/assets/eventmodeling-NTZA5JFV-DONQf5aG.js +0 -1
- package/studio/web/assets/flowDiagram-KWPJA3E3-BoZ8_QR1.js +0 -1
- package/studio/web/assets/gitGraph-4MIJSDKK-BrXWmJt2.js +0 -1
- package/studio/web/assets/gitGraphDiagram-X574FWY7-C2KnnZeY.js +0 -106
- package/studio/web/assets/graphlib-C9Tl7QrK.js +0 -1
- package/studio/web/assets/index-QLZw6xrU.css +0 -1
- package/studio/web/assets/index-qCLyhyah.js +0 -828
- package/studio/web/assets/index.lazy-DCx-DxIV.js +0 -2
- package/studio/web/assets/info-A6RAGUB7-BhzBHe4U.js +0 -1
- package/studio/web/assets/mermaid.core-1PX6R49U.js +0 -44
- package/studio/web/assets/notifications-BSObVIY1.js +0 -1
- package/studio/web/assets/notifications-DFLXk68b.js +0 -1
- package/studio/web/assets/packet-AYTQ26CC-DWbXHx5g.js +0 -1
- package/studio/web/assets/pie-WAS4IAKB-DjoTm3yr.js +0 -1
- package/studio/web/assets/pieDiagram-5QR66LMP-Dz3RHpDA.js +0 -39
- package/studio/web/assets/profile-TBjzOMIy.js +0 -1
- package/studio/web/assets/radar-RG4KPBEZ-CVBgjCOj.js +0 -1
- package/studio/web/assets/railroad-74A4TZTK-BdhXw10Q.js +0 -1
- package/studio/web/assets/railroad-abnf-HS5TGJTU-BcoQPN39.js +0 -1
- package/studio/web/assets/railroad-ebnf-LZEXJU2U-B1Lke2JN.js +0 -1
- package/studio/web/assets/railroad-peg-WCYAUIDC-D-oJFkiV.js +0 -1
- package/studio/web/assets/regions-C4an4H7e.js +0 -1
- package/studio/web/assets/restartGate-nHAFLDh-.js +0 -1
- package/studio/web/assets/sankeyDiagram-IPEJSGJF-D9AyLce2.js +0 -40
- package/studio/web/assets/sequenceDiagram-PO4LG4MO-hfk4aIEm.js +0 -169
- package/studio/web/assets/setComponentFile-BrqOM1Zm.js +0 -1
- package/studio/web/assets/setup-0v2MCm-E.js +0 -2
- package/studio/web/assets/status-DdezofUu.js +0 -61
- package/studio/web/assets/swimlanesDiagram-TC7HE7FX-DfN7BAP5.js +0 -8
- package/studio/web/assets/tabs-BCTQzoQK.js +0 -1
- package/studio/web/assets/treeView-Q6P3EWNA-C-epdxRo.js +0 -1
- package/studio/web/assets/treemap-WGGIJYW6-DOVyp_zw.js +0 -1
- package/studio/web/assets/usecaseDiagram-POWQR4AR-B6252cbq.js +0 -328
- package/studio/web/assets/vendor-datadog-ozKf-pts.js +0 -6
- package/studio/web/assets/vendor-tanstack-Bntfka-M.js +0 -2
- package/studio/web/assets/vendor-ui-9EYYDgoN.js +0 -41
- package/studio/web/assets/wardley-WFR3VGLG-Cfqoe6z4.js +0 -1
- package/studio/web/assets/xychartDiagram-PMCCYNJV-UrOKRsmR.js +0 -7
package/server/DESIGN.md
CHANGED
|
@@ -358,6 +358,7 @@ A persisted QoS 1/2 entry is `{ qos, topic, startTime, databaseGeneration? }`, a
|
|
|
358
358
|
- **Before CONNACK,** a collection entry bound to its database's current generation is checked against the floor (a record entry's own walk decides after CONNACK); a failure deletes the record and reports the session as not present. The connection then writes its incarnation (`takeOver`), and every later write first checks the stored incarnation: a connection that finds another's stops writing and closes (0x8E), and one resumed from a record never creates one. Any newer authenticated connection for the client id on the same thread, a clean start included, closes the older durable connection the same way before it reads or deletes the record, once that connection has saved its positions, and waits for that save to settle; a connection that has already closed counts until its last save settles. A save that failed leaves the record at the last one that landed, so the newer connection resumes from there and may redeliver, never skip. Simultaneous CONNECTs for one client id take their turn through a per-thread claim, so each sees the one before it; one that gives up waiting keeps its place until the one before it finishes. The CONNECT's pending session is assigned before it waits, so packets sent right behind the CONNECT wait with it rather than being refused; the wait is bounded (10 s, then CONNACK 0x88, or 0x03 on v3.1.1), and a CONNECT whose client closes while it waits takes nothing over. A session that has ended, by a disconnect or by yielding to a takeover, saves nothing after its final save, so a SUBSCRIBE still in flight cannot write over the record a newer connection claimed.
|
|
359
359
|
- **Subscription changes are serial.** Packets are handled concurrently, so a durable session applies SUBSCRIBE, UNSUBSCRIBE and `resume()` one at a time: a topic's state belongs to one subscription. A SUBSCRIBE of any QoS is saved before SUBACK. A failed SUBSCRIBE leaves its topic unsubscribed and saves that. It ends the subscription it started, and drops the state of the one it replaced, which replacing ended. An UNSUBSCRIBE whose save fails is reported failed again on a retry until the removal is saved, and any failed save starts the checkpoint timer, so a session with only QoS 0 topics retries it too.
|
|
360
360
|
- **Wills are kept per connection,** keyed `[clientId, incarnation]`: a connection closing after a takeover publishes and deletes only its own will (MQTT 5 §3.1.4 publishes a taken-over client's will), never the will of the connection that replaced it.
|
|
361
|
+
- **Only a will's publish is authorized** (`settleWill`, harper#1874). The `hdb_session_will` row is the session's bookkeeping: it is read and deleted on contexts of its own, not the session's `authorize: true` context, which spends its one-shot check on the first call (roles are not granted system tables, so a non-super user's will never published and its row leaked). The publish then runs on a fresh session context, as the connection's user or a scoped will's embedded role, and the row is deleted after any outcome, a refused or failed publish included, so a will that fails for a transient reason is lost: the boot sweep, the only retry path, would replay a kept row only at the next restart, with no liveness check (harper#3034). The publish commits before the delete, so a crash between them leaves the row for that sweep and the will may be published twice (by design; untested). `unitTests/server/sessionWill.test.js` and `integrationTests/mqtt/will-authorization.test.ts` enforce that only the publish is authorized and that the row is deleted after a publish, a refusal and a clean DISCONNECT.
|
|
361
362
|
- **One terminal path** (`subscriptionFailed`): a refused resume subscribe, a final `Error` value (observed through the subscription's `close` as well as the consumer loop, which may be parked on socket back-pressure), or an iterator failure. Only a 410 (pruned history) discards the session — writers fenced, siblings ended, a v5 DISCONNECT (0x83, the client-error message when problem information is allowed) and a socket close that never waits on storage, then a deletion only if the record still carries this incarnation. Anything else, a 409 from a restored or copied database included, saves positions and closes the connection so the client resumes.
|
|
362
363
|
- **Only the current generation is checked.** A position bound to another generation, another node's (`hdb_durable_session` replicates, and each node mints its own generations) or this database's before a restore or copy, resumes best-effort: it is not checked before CONNACK, replays unchecked from where it is, and binds again at the first certified position. Until a live write gives it a watermark, that replay saves no position, so a reconnect on a quiet database replays it again.
|
|
363
364
|
- **Not covered:** concurrent connections for one client id on different threads, together with crash replay of a failed conditional write (harper#2940); late commits below a saved position, including a late transaction still queued behind the message being sent, which the session cannot see (harper#2928); a replay from another generation, which is unchecked.
|
|
@@ -647,6 +648,7 @@ The SQL and job paths are additive rather than exclusive: `verifyPermsAST` valid
|
|
|
647
648
|
- **Headers commit exactly once, through `this.writeHead`.** `write()`, `end()`, `flushHeaders()` and `_implicitHeader()` all reach `this.writeHead(this.statusCode)` by property lookup, so a `writeHead` that `on-headers` replaced on the instance runs its listeners (the ones that set `Content-Encoding` and remove `Content-Length`) before the promise resolves. After commit, `setHeader`/`appendHeader`/`removeHeader` and a second `writeHead` throw `ERR_HTTP_HEADERS_SENT` as Node's do; `_header` (which `compression` ≤ 1.7 tests instead of `headersSent`) and `finished` (which `on-finished` tests) derive from that state.
|
|
648
649
|
- **The adapter owns the `'error'` listener.** A `destroy(err)` right after `writeHead()` emits before the awaiting caller can attach one; the error stays in the stream's `errored` state for `pipeline()`, `finished()` or async iteration. Client disconnect (`Request.signal`) destroys the response without an error after headers (a plain premature close, which `pipeBodyToResponse` treats as routine) and rejects the promise with the abort reason before them; a handler that throws or rejects before ending the response destroys it, and one that fails after `end()` is logged at warn.
|
|
649
650
|
- **Header names are case-insensitive on removal too.** `Headers.delete` lowercases like `set`/`get`/`has`; the inherited `Map.delete` silently left `send`'s `Content-Length` on a gzip body (truncated transfers).
|
|
651
|
+
- **Every piped chunk reaches `write()` by property lookup.** Node 26.11's `Readable.pipe` fast path (nodejs/node#66182) calls `_write()` directly when pipe's listener is a byte-mode source's only `'data'` listener, so `compression`'s instance patch of `res.write` was skipped for most chunks of `send(...).pipe(res)`: raw bytes inside the gzip stream, or a stall once `compression` had routed pipe's `'drain'` to its idle zlib stream. On runtimes that have the fast path (`'writeKnownBuffer' in Writable`, a static it added) the response adds an inert `'data'` listener to each byte-mode core `Readable` on `'pipe'`, which fails the fast path's single-listener check, and removes it on `'unpipe'` through a listener prepended in the constructor: pipe's own cleanup resumes a source awaiting `'drain'` while any `'data'` listener remains, which would leave an unpiped source flowing into nothing. Object-mode and decoded sources, and legacy `Stream.prototype.pipe` (which never emits `'unpipe'`), are skipped. Remove it once every supported Node 26 release dispatches pipe through an overridden `write()`.
|
|
650
652
|
- **Express is not a target.** `express`'s `app.handle()` replaces the response's prototype with one rooted at `http.ServerResponse.prototype`, which no Writable-derived response survives, and would do the same to the request `Proxy`'s target, Harper's real `IncomingMessage`. Middleware that duck-types the response (Next.js, `compression`, `send`, `serve-static`, `finalhandler`, h3, fastify) is the supported surface.
|
|
651
653
|
|
|
652
654
|
## `manageThreads` has two different `workerCount`s (`server/threads/manageThreads.js`)
|
|
@@ -484,22 +484,7 @@ class SubscriptionsSession {
|
|
|
484
484
|
const willId = this.willId;
|
|
485
485
|
this.willId = undefined;
|
|
486
486
|
if (willId) {
|
|
487
|
-
|
|
488
|
-
transaction(context, async () => {
|
|
489
|
-
try {
|
|
490
|
-
if (!clientTerminated) {
|
|
491
|
-
const will = await getLastWill().get(willId);
|
|
492
|
-
if (will && !isWillFromExpiredScopedToken(will)) {
|
|
493
|
-
// A scoped will authorizes under its own embedded role, never the disconnecting
|
|
494
|
-
// session's user (which may be a later same-clientId reconnect).
|
|
495
|
-
const willContext = will.user?._scopedToken ? { ...context, user: will.user } : context;
|
|
496
|
-
await publishMessage(will, will.data, willContext);
|
|
497
|
-
}
|
|
498
|
-
}
|
|
499
|
-
} finally {
|
|
500
|
-
await getLastWill().delete(willId);
|
|
501
|
-
}
|
|
502
|
-
}).catch((error) => {
|
|
487
|
+
this.settleWill(willId, !clientTerminated).catch((error) => {
|
|
503
488
|
warn(`Error publishing MQTT will for ${this.sessionId}`, error);
|
|
504
489
|
});
|
|
505
490
|
}
|
|
@@ -509,6 +494,23 @@ class SubscriptionsSession {
|
|
|
509
494
|
}
|
|
510
495
|
this.subscriptions = [];
|
|
511
496
|
}
|
|
497
|
+
/** The will row is session bookkeeping on contexts of its own; only the publish runs as the session (server/DESIGN.md). */
|
|
498
|
+
async settleWill(willId: [string, string], publish: boolean) {
|
|
499
|
+
try {
|
|
500
|
+
if (publish) {
|
|
501
|
+
const will = await getLastWill().get(willId, {});
|
|
502
|
+
if (will && !isWillFromExpiredScopedToken(will)) {
|
|
503
|
+
const context = this.createContext();
|
|
504
|
+
// A scoped will authorizes under its own embedded role, never the disconnecting
|
|
505
|
+
// session's user (which may be a later same-clientId reconnect).
|
|
506
|
+
if (will.user?._scopedToken) context.user = will.user;
|
|
507
|
+
await publishMessage(will, will.data, context);
|
|
508
|
+
}
|
|
509
|
+
}
|
|
510
|
+
} finally {
|
|
511
|
+
await getLastWill().delete(willId, {});
|
|
512
|
+
}
|
|
513
|
+
}
|
|
512
514
|
receivedPacket() {
|
|
513
515
|
if (this.keepalive) {
|
|
514
516
|
clearTimeout(this.keepaliveTimer);
|
package/server/mqtt.ts
CHANGED
|
@@ -206,6 +206,19 @@ const SERVER_SHUTTING_DOWN = 0x8b; // MQTT v5 DISCONNECT reason code
|
|
|
206
206
|
const REASON_STRING_LIMIT = 256; // bytes
|
|
207
207
|
/** Fixed header, message id and reason code, plus the property identifier and its length prefix. */
|
|
208
208
|
const ACK_PACKET_OVERHEAD = 16;
|
|
209
|
+
/** `AccessViolation` is 401 for a request with no user and 403 for a user lacking permission; an MQTT client sees both as "Not authorized". */
|
|
210
|
+
const isNotAuthorized = (error: any) => error?.statusCode === 401 || error?.statusCode === 403;
|
|
211
|
+
/** Only failures this layer maps to a code of their own are safe to describe: any other carries an internal message. */
|
|
212
|
+
const hasClientSafeMessage = (error: any) => isNotAuthorized(error) || error?.statusCode === 404;
|
|
213
|
+
/** REASON_STRING_LIMIT counts encoded bytes, and a character must not be cut in half. */
|
|
214
|
+
function boundedReasonString(message: unknown): string {
|
|
215
|
+
const text = String(message);
|
|
216
|
+
if (Buffer.byteLength(text) <= REASON_STRING_LIMIT) return text;
|
|
217
|
+
const bytes = Buffer.from(text, 'utf8');
|
|
218
|
+
let end = REASON_STRING_LIMIT;
|
|
219
|
+
while (end > 0 && (bytes[end] & 0xc0) === 0x80) end--;
|
|
220
|
+
return bytes.subarray(0, end).toString('utf8');
|
|
221
|
+
}
|
|
209
222
|
|
|
210
223
|
/**
|
|
211
224
|
* Every live MQTT connection on this worker, so a shutdown can announce itself: a bare socket close
|
|
@@ -327,17 +340,14 @@ function onSocket(socket, send, request, user, mqttSettings) {
|
|
|
327
340
|
function closeConnection(error?: Error) {
|
|
328
341
|
try {
|
|
329
342
|
if (mqttOptions.protocolVersion >= 5) {
|
|
330
|
-
const packet: any = { cmd: 'disconnect', reasonCode: error ? 0x83 : 0x8e };
|
|
343
|
+
const packet: any = { cmd: 'disconnect', reasonCode: error ? (isNotAuthorized(error) ? 0x87 : 0x83) : 0x8e };
|
|
331
344
|
// as for PUBACK: only failures this layer describes itself are safe to hand a client
|
|
332
345
|
const describable =
|
|
333
346
|
error instanceof DatabaseGenerationChangedError ||
|
|
334
347
|
error instanceof ResumeHistoryUnavailableError ||
|
|
335
|
-
(error
|
|
336
|
-
(error as any)?.statusCode === 404;
|
|
348
|
+
hasClientSafeMessage(error);
|
|
337
349
|
const reasonString =
|
|
338
|
-
describable && error.message && sendProblemInformation
|
|
339
|
-
? Buffer.from(String(error.message), 'utf8').subarray(0, REASON_STRING_LIMIT).toString('utf8')
|
|
340
|
-
: undefined;
|
|
350
|
+
describable && error.message && sendProblemInformation ? boundedReasonString(error.message) : undefined;
|
|
341
351
|
if (
|
|
342
352
|
reasonString &&
|
|
343
353
|
(!maximumPacketSize || maximumPacketSize >= Buffer.byteLength(reasonString) + ACK_PACKET_OVERHEAD)
|
|
@@ -547,7 +557,11 @@ function onSocket(socket, send, request, user, mqttSettings) {
|
|
|
547
557
|
});
|
|
548
558
|
const listener = async (topic, message, messageId, subscription, version) => {
|
|
549
559
|
try {
|
|
550
|
-
if (disconnected)
|
|
560
|
+
if (disconnected) {
|
|
561
|
+
// a delivery already past the subscription when the client left; the disconnect has ended the session
|
|
562
|
+
mqttLog.debug?.(`Session ${session?.sessionId} disconnected while trying to send message to ${topic}`);
|
|
563
|
+
return false;
|
|
564
|
+
}
|
|
551
565
|
const slashIndex = topic.indexOf('/', 1);
|
|
552
566
|
const generalTopic = slashIndex > 0 ? topic.slice(0, slashIndex) : topic;
|
|
553
567
|
const qos = subscription.qos || 0;
|
|
@@ -606,6 +620,8 @@ function onSocket(socket, send, request, user, mqttSettings) {
|
|
|
606
620
|
break;
|
|
607
621
|
case 'subscribe':
|
|
608
622
|
const granted = [];
|
|
623
|
+
// a SUBACK carries one reason string for the whole packet, so the first refusal's is the one sent
|
|
624
|
+
let refusalMessage: string | undefined;
|
|
609
625
|
for (const subscription of packet.subscriptions) {
|
|
610
626
|
let grantedQos;
|
|
611
627
|
try {
|
|
@@ -624,21 +640,27 @@ function onSocket(socket, send, request, user, mqttSettings) {
|
|
|
624
640
|
grantedQos =
|
|
625
641
|
mqttOptions.protocolVersion < 5
|
|
626
642
|
? 0x80 // the only error code in v3.1.1
|
|
627
|
-
: error
|
|
643
|
+
: isNotAuthorized(error)
|
|
628
644
|
? 0x87 // unauthorized
|
|
629
645
|
: error.statusCode === 404
|
|
630
646
|
? 0x8f // invalid topic
|
|
631
647
|
: 0x80; // generic failure
|
|
648
|
+
if (mqttOptions.protocolVersion >= 5 && error.message && hasClientSafeMessage(error))
|
|
649
|
+
refusalMessage ??= error.message;
|
|
632
650
|
}
|
|
633
651
|
granted.push(grantedQos);
|
|
634
652
|
}
|
|
635
653
|
await session.committed;
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
|
|
641
|
-
|
|
654
|
+
const subackPacket: any = { cmd: 'suback', granted, messageId: packet.messageId };
|
|
655
|
+
if (refusalMessage !== undefined && sendProblemInformation)
|
|
656
|
+
subackPacket.properties = { reasonString: boundedReasonString(refusalMessage) };
|
|
657
|
+
let suback = generate(subackPacket, mqttOptions);
|
|
658
|
+
// the result vector grows with the filter count, so only the encoded packet says whether the string fits
|
|
659
|
+
if (subackPacket.properties && maximumPacketSize && suback.length > maximumPacketSize) {
|
|
660
|
+
delete subackPacket.properties;
|
|
661
|
+
suback = generate(subackPacket, mqttOptions);
|
|
662
|
+
}
|
|
663
|
+
sendPacket(suback, 'suback');
|
|
642
664
|
break;
|
|
643
665
|
case 'unsubscribe': {
|
|
644
666
|
const granted = [];
|
|
@@ -691,23 +713,18 @@ function onSocket(socket, send, request, user, mqttSettings) {
|
|
|
691
713
|
reasonCode:
|
|
692
714
|
mqttOptions.protocolVersion < 5
|
|
693
715
|
? 0x80 // the only error code in v3.1.1
|
|
694
|
-
: error
|
|
716
|
+
: isNotAuthorized(error)
|
|
695
717
|
? 0x87 // not authorized
|
|
696
718
|
: error?.statusCode === 404
|
|
697
719
|
? 0x90 // topic name invalid
|
|
698
720
|
: 0x80, // unspecified error
|
|
699
721
|
};
|
|
700
|
-
// Only errors this layer maps to a code of their own are safe to describe: any other
|
|
701
|
-
// failure carries an internal message that a client must not be handed. Both limits
|
|
702
|
-
// are in encoded bytes, so measure and trim the string in bytes too.
|
|
703
722
|
const describable =
|
|
704
723
|
mqttOptions.protocolVersion >= 5 &&
|
|
705
724
|
sendProblemInformation &&
|
|
706
725
|
error?.message &&
|
|
707
|
-
(error
|
|
708
|
-
const reasonString = describable
|
|
709
|
-
? Buffer.from(String(error.message), 'utf8').subarray(0, REASON_STRING_LIMIT).toString('utf8')
|
|
710
|
-
: undefined;
|
|
726
|
+
hasClientSafeMessage(error);
|
|
727
|
+
const reasonString = describable ? boundedReasonString(error.message) : undefined;
|
|
711
728
|
if (
|
|
712
729
|
reasonString &&
|
|
713
730
|
(!maximumPacketSize || maximumPacketSize >= Buffer.byteLength(reasonString) + ACK_PACKET_OVERHEAD)
|
|
@@ -6,7 +6,7 @@ import type {
|
|
|
6
6
|
ServerResponse as NodeServerResponse,
|
|
7
7
|
} from 'node:http';
|
|
8
8
|
import type { Socket } from 'node:net';
|
|
9
|
-
import { PassThrough } from 'node:stream';
|
|
9
|
+
import { PassThrough, Readable, Writable } from 'node:stream';
|
|
10
10
|
import { Headers as ResponseHeaders, applyWriteHeadHeaders } from './Headers.ts';
|
|
11
11
|
|
|
12
12
|
export interface AdaptedResponse {
|
|
@@ -29,6 +29,20 @@ class UnsupportedResponseMethodError extends Error {
|
|
|
29
29
|
}
|
|
30
30
|
|
|
31
31
|
const ignoreError = () => {};
|
|
32
|
+
const ignoreChunk = () => {};
|
|
33
|
+
|
|
34
|
+
// Node 26.11's pipe fast path (nodejs/node#66182) skips write() overrides such as compression's while pipe's listener
|
|
35
|
+
// is a byte-mode source's only 'data' listener; see server/DESIGN.md
|
|
36
|
+
const pipeSkipsWrite = 'writeKnownBuffer' in Writable;
|
|
37
|
+
function guardPipeSource(source: Readable) {
|
|
38
|
+
// object mode, a decoder, and legacy or userland pipe() never take the fast path; legacy pipe() never emits 'unpipe'
|
|
39
|
+
if (source instanceof Readable && !source.readableObjectMode && !source.readableEncoding)
|
|
40
|
+
source.on('data', ignoreChunk);
|
|
41
|
+
}
|
|
42
|
+
// must run before pipe's own unpipe cleanup, which resumes a source awaiting 'drain' while any 'data' listener remains
|
|
43
|
+
function releasePipeSource(source: unknown) {
|
|
44
|
+
if (source instanceof Readable) source.removeListener('data', ignoreChunk);
|
|
45
|
+
}
|
|
32
46
|
|
|
33
47
|
export class NodeAdapterResponse extends PassThrough implements NodeServerResponse {
|
|
34
48
|
statusCode = 200;
|
|
@@ -60,6 +74,10 @@ export class NodeAdapterResponse extends PassThrough implements NodeServerRespon
|
|
|
60
74
|
this.#resolve = resolve;
|
|
61
75
|
this.#reject = reject;
|
|
62
76
|
this.on('error', ignoreError);
|
|
77
|
+
if (pipeSkipsWrite) {
|
|
78
|
+
this.on('pipe', guardPipeSource);
|
|
79
|
+
this.prependListener('unpipe', releasePipeSource);
|
|
80
|
+
}
|
|
63
81
|
if (typeof nodeResponse?.on === 'function') {
|
|
64
82
|
const forward = (...args: any[]) => this.emit('timeout', ...args);
|
|
65
83
|
let forwarding = false;
|