@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
|
@@ -84,6 +84,8 @@ export function addSubscription(table, key, listener?: (key) => any, startTime?:
|
|
|
84
84
|
tableSubscriptions.set(key, (subscriptions = [subscription]));
|
|
85
85
|
subscriptions.tables = tableSubscriptions;
|
|
86
86
|
subscriptions.key = key;
|
|
87
|
+
subscriptions.traversals = 0;
|
|
88
|
+
subscriptions.hasEnded = false;
|
|
87
89
|
}
|
|
88
90
|
subscription.subscriptions = subscriptions;
|
|
89
91
|
databaseSubscriptions.activeCount = (databaseSubscriptions.activeCount || 0) + 1;
|
|
@@ -107,23 +109,18 @@ class Subscription extends IterableEventQueue {
|
|
|
107
109
|
this.on('close', () => this.end());
|
|
108
110
|
}
|
|
109
111
|
end() {
|
|
110
|
-
|
|
111
|
-
if (
|
|
112
|
-
const tableSubscriptions = this.subscriptions.tables;
|
|
113
|
-
const envSubscriptions = tableSubscriptions?.envs;
|
|
114
|
-
this.subscriptions.splice(this.subscriptions.indexOf(this), 1);
|
|
115
|
-
if (this.subscriptions.length === 0) {
|
|
116
|
-
if (tableSubscriptions) {
|
|
117
|
-
// TODO: Handle cleanup of wildcard
|
|
118
|
-
const key = this.subscriptions.key;
|
|
119
|
-
tableSubscriptions.delete(key);
|
|
120
|
-
if (tableSubscriptions.size === 0) {
|
|
121
|
-
delete envSubscriptions[tableSubscriptions.tableId];
|
|
122
|
-
}
|
|
123
|
-
}
|
|
124
|
-
}
|
|
125
|
-
if (envSubscriptions?.activeCount > 0) envSubscriptions.activeCount--;
|
|
112
|
+
const subscriptions = this.subscriptions;
|
|
113
|
+
if (subscriptions) {
|
|
126
114
|
this.subscriptions = null;
|
|
115
|
+
const envSubscriptions = subscriptions.tables?.envs;
|
|
116
|
+
if (envSubscriptions?.activeCount > 0) envSubscriptions.activeCount--;
|
|
117
|
+
// splicing would shift the next subscriber past a loop walking this array, which compacts it when it finishes
|
|
118
|
+
if (subscriptions.traversals > 0) subscriptions.hasEnded = true;
|
|
119
|
+
else {
|
|
120
|
+
const index = subscriptions.indexOf(this);
|
|
121
|
+
if (index > -1) subscriptions.splice(index, 1);
|
|
122
|
+
if (subscriptions.length === 0) detachKeySubscriptions(subscriptions);
|
|
123
|
+
}
|
|
127
124
|
}
|
|
128
125
|
this.close();
|
|
129
126
|
}
|
|
@@ -131,6 +128,26 @@ class Subscription extends IterableEventQueue {
|
|
|
131
128
|
return { name: 'subscription' };
|
|
132
129
|
}
|
|
133
130
|
}
|
|
131
|
+
function endTraversal(keySubscriptions) {
|
|
132
|
+
if (--keySubscriptions.traversals === 0 && keySubscriptions.hasEnded) {
|
|
133
|
+
keySubscriptions.hasEnded = false;
|
|
134
|
+
let kept = 0;
|
|
135
|
+
for (let i = 0; i < keySubscriptions.length; i++) {
|
|
136
|
+
const subscription = keySubscriptions[i];
|
|
137
|
+
if (subscription.subscriptions) keySubscriptions[kept++] = subscription;
|
|
138
|
+
}
|
|
139
|
+
keySubscriptions.length = kept;
|
|
140
|
+
if (kept === 0) detachKeySubscriptions(keySubscriptions);
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
function detachKeySubscriptions(keySubscriptions) {
|
|
144
|
+
const tableSubscriptions = keySubscriptions.tables;
|
|
145
|
+
if (tableSubscriptions) {
|
|
146
|
+
// TODO: Handle cleanup of wildcard
|
|
147
|
+
tableSubscriptions.delete(keySubscriptions.key);
|
|
148
|
+
if (tableSubscriptions.size === 0) delete tableSubscriptions.envs[tableSubscriptions.tableId];
|
|
149
|
+
}
|
|
150
|
+
}
|
|
134
151
|
const ACTIONS_OF_INTEREST = ['put', 'patch', 'delete', 'message', 'invalidate'];
|
|
135
152
|
// Maximum audit records processed per synchronous turn before yielding back to the event loop.
|
|
136
153
|
// Sized to keep per-batch wall time within a few ms on commodity hardware while keeping the
|
|
@@ -196,33 +213,41 @@ function notifyFromTransactionData(subscriptions, auditLogIterable?, allowYield
|
|
|
196
213
|
// this allows for efficient subscriptions to children ids/topics
|
|
197
214
|
const keySubscriptions = tableSubscriptions.get(matchingKey);
|
|
198
215
|
if (keySubscriptions) {
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
continue;
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
if (subscription.
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
beginTxn
|
|
215
|
-
if (
|
|
216
|
-
// if
|
|
217
|
-
|
|
218
|
-
|
|
216
|
+
keySubscriptions.traversals++;
|
|
217
|
+
try {
|
|
218
|
+
// a subscriber added during this walk starts with the next record
|
|
219
|
+
for (let i = 0, length = keySubscriptions.length; i < length; i++) {
|
|
220
|
+
const subscription = keySubscriptions[i];
|
|
221
|
+
if (!subscription.subscriptions) continue;
|
|
222
|
+
if (
|
|
223
|
+
ancestorLevel > 0 && // only ancestors if the subscription is for ancestors (and apply onlyChildren filtering as necessary)
|
|
224
|
+
!(subscription.includeDescendants && !(subscription.onlyChildren && ancestorLevel > 1))
|
|
225
|
+
)
|
|
226
|
+
continue;
|
|
227
|
+
if (subscription.startTime >= timestamp) {
|
|
228
|
+
continue;
|
|
229
|
+
}
|
|
230
|
+
try {
|
|
231
|
+
let beginTxn;
|
|
232
|
+
if (subscription.supportsTransactions && subscription.txnInProgress !== txnKey) {
|
|
233
|
+
// if the subscriber supports transactions, we mark this as the beginning of a new transaction
|
|
234
|
+
// tracking the subscription so that we can delimit the transaction on next transaction
|
|
235
|
+
// (with a beginTxn flag, which may be on an endTxn event)
|
|
236
|
+
beginTxn = true;
|
|
237
|
+
if (!subscription.txnInProgress) {
|
|
238
|
+
// if first txn for subscriber of this cycle, add to the transactional subscribers that we are tracking
|
|
239
|
+
if (!subscribersWithTxns) subscribersWithTxns = [subscription];
|
|
240
|
+
else subscribersWithTxns.push(subscription);
|
|
241
|
+
}
|
|
242
|
+
subscription.txnInProgress = txnKey;
|
|
219
243
|
}
|
|
220
|
-
subscription.
|
|
244
|
+
subscription.listener(recordId, auditRecord, timestamp, beginTxn);
|
|
245
|
+
} catch (error) {
|
|
246
|
+
warn(error);
|
|
221
247
|
}
|
|
222
|
-
subscription.listener(recordId, auditRecord, timestamp, beginTxn);
|
|
223
|
-
} catch (error) {
|
|
224
|
-
warn(error);
|
|
225
248
|
}
|
|
249
|
+
} finally {
|
|
250
|
+
endTraversal(keySubscriptions);
|
|
226
251
|
}
|
|
227
252
|
}
|
|
228
253
|
if (matchingKey == null) break;
|
|
@@ -242,14 +267,23 @@ function notifyFromTransactionData(subscriptions, auditLogIterable?, allowYield
|
|
|
242
267
|
// the bulk-reloaded table. hdb_nodes peer discovery and hdb_certificate CA install rely on this.
|
|
243
268
|
const tableSubscriptions = subscriptions[auditRecord.tableId];
|
|
244
269
|
if (tableSubscriptions) {
|
|
270
|
+
// keys added during the walk are appended, so this cap keeps listeners from extending it
|
|
271
|
+
let remainingKeys = tableSubscriptions.size;
|
|
245
272
|
for (const keySubscriptions of tableSubscriptions.values()) {
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
273
|
+
if (remainingKeys-- === 0) break;
|
|
274
|
+
keySubscriptions.traversals++;
|
|
275
|
+
try {
|
|
276
|
+
for (let i = 0, length = keySubscriptions.length; i < length; i++) {
|
|
277
|
+
const subscription = keySubscriptions[i];
|
|
278
|
+
if (!subscription.subscriptions || subscription.startTime >= timestamp) continue;
|
|
279
|
+
try {
|
|
280
|
+
subscription.listener(null, auditRecord, timestamp, false);
|
|
281
|
+
} catch (error) {
|
|
282
|
+
warn(error);
|
|
283
|
+
}
|
|
252
284
|
}
|
|
285
|
+
} finally {
|
|
286
|
+
endTraversal(keySubscriptions);
|
|
253
287
|
}
|
|
254
288
|
}
|
|
255
289
|
}
|
|
@@ -272,7 +306,12 @@ function notifyFromTransactionData(subscriptions, auditLogIterable?, allowYield
|
|
|
272
306
|
// any subscribers with open transactions need to have an event to indicate that their transaction has been ended
|
|
273
307
|
for (const subscription of subscribersWithTxns) {
|
|
274
308
|
subscription.txnInProgress = null; // clean up
|
|
275
|
-
subscription.
|
|
309
|
+
if (!subscription.subscriptions) continue;
|
|
310
|
+
try {
|
|
311
|
+
subscription.listener(null, { type: 'end_txn' }, subscriptions.lastTxnTime, true);
|
|
312
|
+
} catch (error) {
|
|
313
|
+
warn(error);
|
|
314
|
+
}
|
|
276
315
|
}
|
|
277
316
|
}
|
|
278
317
|
} finally {
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
// Exact identity rather than mutual assignability, which lets `any`, dropped overloads and
|
|
2
|
+
// bivariant method parameters pass unnoticed.
|
|
3
|
+
export type ExactlyEqual<A, B> = (<T>() => T extends A ? 1 : 2) extends <T>() => T extends B ? 1 : 2 ? true : false;
|
|
4
|
+
|
|
5
|
+
export type MemberDrift<Actual, Declared> =
|
|
6
|
+
| Exclude<keyof Actual, keyof Declared>
|
|
7
|
+
| Exclude<keyof Declared, keyof Actual>
|
|
8
|
+
| {
|
|
9
|
+
[Key in keyof Actual & keyof Declared]: ExactlyEqual<Pick<Actual, Key>, Pick<Declared, Key>> extends true
|
|
10
|
+
? never
|
|
11
|
+
: Key;
|
|
12
|
+
}[keyof Actual & keyof Declared];
|
|
13
|
+
|
|
14
|
+
export type AssertNoDrift<Drift extends never> = Drift;
|
|
15
|
+
|
|
16
|
+
export type AssertTrue<Condition extends true> = Condition;
|
|
17
|
+
|
|
18
|
+
export interface ParitySentinel {
|
|
19
|
+
readonly paritySentinel: true;
|
|
20
|
+
}
|
package/schema.graphql
CHANGED
|
@@ -222,7 +222,14 @@ A source attribute included in a full-text index.
|
|
|
222
222
|
input FullTextSource {
|
|
223
223
|
name: String!
|
|
224
224
|
weight: Float! = 1.0
|
|
225
|
+
"""
|
|
226
|
+
Enable highlighting for this source. At least one source must opt in when @fullText.highlighting is set.
|
|
227
|
+
"""
|
|
225
228
|
highlight: Boolean
|
|
229
|
+
"""
|
|
230
|
+
Required for Blob sources. Only "text/plain" is supported.
|
|
231
|
+
"""
|
|
232
|
+
mediaType: String
|
|
226
233
|
}
|
|
227
234
|
|
|
228
235
|
"""
|
|
@@ -234,7 +241,8 @@ input FullTextSynonymRule {
|
|
|
234
241
|
}
|
|
235
242
|
|
|
236
243
|
"""
|
|
237
|
-
Opt-in highlighting limits. Omitting highlighting from @fullText disables it.
|
|
244
|
+
Opt-in highlighting limits. Omitting highlighting from @fullText disables it. At least one source
|
|
245
|
+
must set highlight: true when these limits are present.
|
|
238
246
|
"""
|
|
239
247
|
input FullTextHighlighting {
|
|
240
248
|
maxFragments: Int! = 3
|
|
@@ -242,18 +250,23 @@ input FullTextHighlighting {
|
|
|
242
250
|
}
|
|
243
251
|
|
|
244
252
|
"""
|
|
245
|
-
|
|
253
|
+
A query-only field naming a full-text index. It has no stored or selectable record value.
|
|
254
|
+
"""
|
|
255
|
+
scalar FullText
|
|
256
|
+
|
|
257
|
+
"""
|
|
258
|
+
Create a native full-text derived index over the listed source attributes. Declare it on a
|
|
259
|
+
FullText field; the field name identifies the index.
|
|
246
260
|
"""
|
|
247
261
|
directive @fullText(
|
|
248
|
-
name: String!
|
|
249
262
|
fields: [FullTextSource!]!
|
|
250
|
-
analyzer: String! = "english@
|
|
263
|
+
analyzer: String! = "english@2"
|
|
251
264
|
stopWords: Boolean! = true
|
|
252
265
|
positions: Boolean! = true
|
|
253
266
|
surfaceTerms: Boolean! = true
|
|
254
267
|
synonyms: [FullTextSynonymRule!]! = []
|
|
255
268
|
highlighting: FullTextHighlighting
|
|
256
|
-
)
|
|
269
|
+
) on FIELD_DEFINITION
|
|
257
270
|
|
|
258
271
|
"""
|
|
259
272
|
Define a derived field whose value is computed from other fields.
|
package/server/DESIGN.md
CHANGED
|
@@ -575,6 +575,8 @@ port so the gap is visible instead of silent.
|
|
|
575
575
|
|
|
576
576
|
`broadcastWithAcknowledgement` already times out (30 s) on a worker whose port stays open but never acks, and that shape is almost always a blocked event loop — a native lock, a runaway synchronous call — which nothing inside the worker can report (harper-pro#788: a restarted node's single http worker went byte-silent while main kept serving `cluster_status`, and the app log only said "not acknowledged by worker thread(s) 2"). So each worker posts its Linux thread id (`readlink /proc/thread-self`) to main once at startup, before anything else runs on it, and the timeout branch reads that thread's kernel state from `/proc/self/task/<tid>`: state, `wchan`, the syscall number (the first token only — the rest of that file is argument registers and stack/instruction pointers), CPU ticks, and context-switch counts, plus two cross-platform signals main already has, `worker.performance.eventLoopUtilization()` and the age of the last 1 s resource report. It samples again a second later and logs the deltas: no CPU ticks, no context switches and `event loop active +1000ms` is "parked on a lock"; ticks climbing with state `R` is "spinning". It is deliberately main-thread-only and best-effort: `workers` and the tid live on the main thread's `Worker` objects, every `/proc` field is reported individually (a hardened container may deny `wchan`/`syscall` while `stat` stays readable), a follow-up sample whose `starttime` differs from the first is discarded (the tid may have been recycled), one diagnostic runs per worker with a 30 s cooldown so concurrent timeouts on the same worker don't multiply reads, and nothing here runs when acks arrive on time. It does not name the lock owner; that still needs a native stack from the next occurrence.
|
|
577
577
|
|
|
578
|
+
That diagnostic is reactive — it only fires once something else (an ITC ack) has already timed out. `sampleWorkerELU`, run from the same 1 s monitoring tick that already computes `recentELU`, is the proactive counterpart: it warns as soon as a worker's `eventLoopUtilization()` stays `>= 0.99` for `PINNED_ELU_SUSTAINED_TICKS` (30) consecutive ticks, and warns once more on recovery. State (`pinnedELUTicks`, `pinnedELUWarned`) lives on the `Worker` object, so a replaced worker starts a clean streak, and a single tick below threshold resets it — no partial credit across a dip. The very first sample after a worker (re)starts is skipped: `worker.performance.eventLoopUtilization()` called with no prior baseline returns the worker's whole-lifetime total, not a 1 s delta, so counting it could misattribute pre-tick startup work as a pinned tick. That baseline check requires a nonzero `idle` or `active` on the prior sample, not just a prior sample existing: before a worker's loop is online, `eventLoopUtilization()` returns a truthy-but-empty `{ idle: 0, active: 0 }`, and treating that as a real baseline would let the following tick's delta span the same whole-lifetime window. Bun has no `eventLoopUtilization()` and reports a placeholder `{ utilization: 0 }` instead (see `sampleWorkerELU`'s Bun branch), so this warning is Node-only. A worker tearing down can briefly report `idle < 0`, putting `utilization` outside `[0, 1]`; that tick is treated as unmeasured — neither counted nor a streak reset — rather than logging an absurd percentage or ending an episode early. Both log lines name the worker by thread id plus `name`/`application` (e.g. a job or isolated-application worker), since a legitimately saturated worker looks identical to a wedged one from ELU alone; this warns on saturation regardless of cause; distinguishing "busy" from "stuck" is left to the operator.
|
|
579
|
+
|
|
578
580
|
## A worker's `parentPort` close is not the main thread's exit (`server/threads/manageThreads.js`)
|
|
579
581
|
|
|
580
582
|
`REMOVE_PORT {threadId}` and the `onThreadExit` listeners (database-drop owner release, deploy lifecycle, registered operations, log rotation) act on a thread's death. A sibling port's `close` is taken as the far thread's exit. A worker's `parentPort` carries `threadId = 0` so lookups can route to main, but its `close` means this worker is leaving: main's exit ends the process. `addPort`'s close listener therefore passes no dead thread id for `parentPort`. Announcing one would make every sibling fail its pending acks to main, drop its own `parentPort`, and fire exit listeners for main. Regression cover: `unitTests/server/threads/stuckWorkerDiagnostics.test.js`, "does not report the main thread as exited to a sibling of a worker that closes its parentPort".
|
|
@@ -662,3 +664,19 @@ gate is per instance on purpose: a shared one let a cache-fill shed silence the
|
|
|
662
664
|
Reproduce with a large concurrent non-GET burst on one CPU-starved worker
|
|
663
665
|
(`integrationTests/resources/sourcedfrom-eav-cache-coherence.test.ts` P1 under Bun pinned to a contended
|
|
664
666
|
core).
|
|
667
|
+
|
|
668
|
+
## Response compression dispatches on the serialized output, with one brotli policy (`server/serverHelpers/contentTypes.ts`)
|
|
669
|
+
|
|
670
|
+
With `http.compressionThreshold` non-zero (the shipped default is 0, off) and `Accept-Encoding: br`, `serialize()` compresses. It chooses between `serialize` and `serializeStream` by the _input_ (iterable or not), but it chooses how to compress by what the serializer _returned_: a `serializeStream` handler may return a complete body (the built-in msgpack handler returns `pack(data)` for a plain array; third-party `server.contentTypes` handlers may return strings or generators). A string or byte body takes the single-buffer path, where the threshold applies; a Readable is compressed as-is; any other iterable is adapted with `Readable.from`, as `http.ts` does for uncompressed iterable bodies. Piping a non-stream failed the request (#2421).
|
|
671
|
+
|
|
672
|
+
The compressor is attached with `stream.pipeline`, not `.pipe()`: `.pipe()` does not forward a source error, so a source failing mid-stream left the compressor open and the response hanging. With `pipeline` the failure destroys the compressor, which the transport reports and closes abruptly (next section).
|
|
673
|
+
|
|
674
|
+
Both compressor call sites take their parameters from `brotliOptions(contentType)`. Node's brotli default is quality 11, which compresses at about 1–2 MB/s — seconds of libuv-pool CPU for a multi-megabyte response — while quality 2 runs at 400–600 MB/s for a third to a half more bytes than 11 on JSON. A second call site with its own parameters is how the single-buffer path came to run at quality 11 while the stream path ran at 2; keep one definition. The operations API's Fastify compression (`operationsServer.ts`) is configured separately.
|
|
675
|
+
|
|
676
|
+
## A streamed response is completed only when its source ends cleanly (`server/http.ts`, `server/serverHelpers/uwsServer.ts`)
|
|
677
|
+
|
|
678
|
+
A response stream that fails after its first byte has no status left to report the failure with. The only signal HTTP/1.1 has is an incomplete message: the connection closes without the chunked terminator. So each transport sink ends the response for a source `'end'` and aborts the connection for anything else — a source `'error'`, or a `'close'` with no `'end'`. Ending cleanly instead hands the client a truncated body framed as a complete 200; formats that truncate at a record boundary (CSV, NDJSON, a msgpack sequence) then parse as a valid shorter result.
|
|
679
|
+
|
|
680
|
+
Node gets this from `stream.pipeline` in `pipeBodyToResponse`. uWS has no Writable to pipe into, so `streamResponse` wires the events itself and calls `res.close()`, which also runs `onAborted` and aborts the request signal. It also checks `source.destroyed`: the uWS handler resolves asynchronously, so a source can be destroyed, and its `'close'` already emitted, before the sink listens for it — the response would stay open forever. The `'error'` listener goes on before that check, because `destroy(error)` emits on a later tick and an `'error'` with no listener is thrown. Under Bun the main port is a `node:http` server and takes the `pipeBodyToResponse` path; the `Bun.serve` fetch handler buffers a stream body before it responds, so a failed source there is an ordinary error response.
|
|
681
|
+
|
|
682
|
+
This is the transport's fallback, not the error contract. SSE and NDJSON GETs hold their first step so an immediate failure is still an HTTP status, and they report later failures in-band ([Streaming startup errors](#streaming-startup-errors)). Every other format commits when the handler returns the stream, so a failure before its first chunk aborts the connection too. Pinned by `unitTests/server/serverHelpers/uwsServer.test.js` ("a response stream that does not end cleanly") and, end to end on every runtime leg, by `integrationTests/server/response-compression.test.ts`.
|
package/server/REST.ts
CHANGED
|
@@ -620,7 +620,7 @@ export function handleApplication(scope: import('../components/Scope.ts').Scope)
|
|
|
620
620
|
let result;
|
|
621
621
|
while (!(result = await iterator.next()).done) {
|
|
622
622
|
const messageBinary = await serializeMessage(result.value, request);
|
|
623
|
-
ws.send(messageBinary);
|
|
623
|
+
ws.send(messageBinary as string | Buffer<ArrayBuffer>);
|
|
624
624
|
recordAction(messageBinary.length, 'bytes-sent', request.handlerPath, 'message', 'ws');
|
|
625
625
|
if ((ws as any)._socket.writableNeedDrain) {
|
|
626
626
|
await new Promise((resolve) => (ws as any)._socket.once('drain', resolve));
|
|
@@ -6,6 +6,10 @@ const hdbTerms = require('../../utility/hdbTerms.ts');
|
|
|
6
6
|
const cleanLmdbMap =
|
|
7
7
|
require('../../utility/lmdb/cleanLMDBMap.ts').default || require('../../utility/lmdb/cleanLMDBMap.ts');
|
|
8
8
|
const { validateEvent } = require('../threads/itc.js');
|
|
9
|
+
const {
|
|
10
|
+
FULL_TEXT_QUERY_PAUSE_OPERATION,
|
|
11
|
+
FULL_TEXT_QUERY_RESUME_OPERATION,
|
|
12
|
+
} = require('../../resources/indexes/fullTextQueryProtocol.ts');
|
|
9
13
|
const { isMainThread, threadId, workerData } = require('node:worker_threads');
|
|
10
14
|
const {
|
|
11
15
|
databases,
|
|
@@ -16,6 +20,7 @@ const {
|
|
|
16
20
|
reloadBranchAt,
|
|
17
21
|
markDropInProgress,
|
|
18
22
|
} = require('../../resources/databases.ts');
|
|
23
|
+
const { blockBlobSavesForRestore, resumeBlobSavesAfterRestore } = require('../../resources/blob.ts');
|
|
19
24
|
|
|
20
25
|
/**
|
|
21
26
|
* This object/functions are passed to the ITC client instance and dynamically added as event handlers.
|
|
@@ -46,6 +51,33 @@ async function schemaHandler(event) {
|
|
|
46
51
|
hdbLogger.error(validate);
|
|
47
52
|
return;
|
|
48
53
|
}
|
|
54
|
+
if (
|
|
55
|
+
event.message?.operation === FULL_TEXT_QUERY_PAUSE_OPERATION ||
|
|
56
|
+
event.message?.operation === FULL_TEXT_QUERY_RESUME_OPERATION
|
|
57
|
+
) {
|
|
58
|
+
if (typeof event.message.path !== 'string' || event.message.path.length === 0)
|
|
59
|
+
throw new Error('Full-text query reader coordination requires an index path');
|
|
60
|
+
if (typeof event.message.readinessId !== 'string' || event.message.readinessId.length === 0)
|
|
61
|
+
throw new Error('Full-text query reader coordination requires a readiness id');
|
|
62
|
+
if (typeof event.message.ownerEpoch !== 'string' || !/^(?:0|[1-9]\d*)$/.test(event.message.ownerEpoch))
|
|
63
|
+
throw new Error('Full-text query reader coordination requires an owner epoch');
|
|
64
|
+
if (
|
|
65
|
+
event.message.allowUnregisteredReadiness !== undefined &&
|
|
66
|
+
typeof event.message.allowUnregisteredReadiness !== 'boolean'
|
|
67
|
+
)
|
|
68
|
+
throw new Error('Full-text query reader coordination requires a boolean unregistered-readiness flag');
|
|
69
|
+
const fullTextQueries = require('../../resources/indexes/fullTextQueryIndex.ts');
|
|
70
|
+
const ownerEpoch = BigInt(event.message.ownerEpoch);
|
|
71
|
+
if (event.message.operation === FULL_TEXT_QUERY_PAUSE_OPERATION)
|
|
72
|
+
await fullTextQueries.pauseNativeFullTextQueryReaders(
|
|
73
|
+
event.message.path,
|
|
74
|
+
event.message.readinessId,
|
|
75
|
+
ownerEpoch,
|
|
76
|
+
event.message.allowUnregisteredReadiness === true
|
|
77
|
+
);
|
|
78
|
+
else fullTextQueries.resumeNativeFullTextQueryReaders(event.message.path, event.message.readinessId, ownerEpoch);
|
|
79
|
+
return;
|
|
80
|
+
}
|
|
49
81
|
|
|
50
82
|
hdbLogger.trace(`ITC schemaHandler received schema event:`, event);
|
|
51
83
|
if (event.message?.operation === hdbTerms.OPERATIONS_ENUM.DROP_SCHEMA && event.message.schema) {
|
|
@@ -71,17 +103,38 @@ async function schemaHandler(event) {
|
|
|
71
103
|
// restore_backup: this thread must release its store handles so the restore can purge and
|
|
72
104
|
// rewrite the database directory. The rescan below (resetDatabases) skips reloading it while
|
|
73
105
|
// the restoring marker is present, and reloads it on the completion signal (marker gone).
|
|
106
|
+
let resumeBlobSavesFor;
|
|
74
107
|
if (event.message?.operation === hdbTerms.OPERATIONS_ENUM.RESTORE_BACKUP && event.message.schema) {
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
108
|
+
if (event.message.restorePhase === 'reload') {
|
|
109
|
+
resumeBlobSavesFor = {
|
|
110
|
+
database: event.message.schema,
|
|
111
|
+
token: event.message.restoreToken,
|
|
112
|
+
generationReplaced: event.message.generationReplaced !== false,
|
|
113
|
+
};
|
|
114
|
+
} else {
|
|
115
|
+
// Stop and drain blob writes before releasing the handles: closing a database is not a
|
|
116
|
+
// write barrier on its own, because a save's file pipeline outlives the handle it started from.
|
|
117
|
+
await blockBlobSavesForRestore(event.message.schema, event.message.restoreToken);
|
|
118
|
+
try {
|
|
119
|
+
await closeDatabase(event.message.schema);
|
|
120
|
+
} catch (error) {
|
|
121
|
+
// Let the originator's process-wide closure check fail the restore immediately instead of
|
|
122
|
+
// withholding this worker's acknowledgement until the broadcast timeout.
|
|
123
|
+
hdbLogger.error(`Could not release database '${event.message.schema}' for restore`, error);
|
|
124
|
+
}
|
|
81
125
|
}
|
|
82
126
|
}
|
|
83
|
-
|
|
84
|
-
|
|
127
|
+
try {
|
|
128
|
+
await cleanLmdbMap(event.message);
|
|
129
|
+
await syncSchemaMetadata(event.message);
|
|
130
|
+
} finally {
|
|
131
|
+
if (resumeBlobSavesFor)
|
|
132
|
+
resumeBlobSavesAfterRestore(
|
|
133
|
+
resumeBlobSavesFor.database,
|
|
134
|
+
resumeBlobSavesFor.token,
|
|
135
|
+
resumeBlobSavesFor.generationReplaced
|
|
136
|
+
);
|
|
137
|
+
}
|
|
85
138
|
for (let listener of schemaListeners) {
|
|
86
139
|
try {
|
|
87
140
|
listener(event?.message);
|
|
@@ -414,8 +414,15 @@ export function findBestSerializer(incomingMessage) {
|
|
|
414
414
|
return { serializer: bestSerializer, type: bestType, parameters: bestParameters };
|
|
415
415
|
}
|
|
416
416
|
|
|
417
|
-
// about an average TCP packet size (if headers included)
|
|
418
417
|
const COMPRESSION_THRESHOLD = envMgr.get(CONFIG_PARAMS.HTTP_COMPRESSIONTHRESHOLD);
|
|
418
|
+
const brotliParams = (mode: number) => ({
|
|
419
|
+
params: { [constants.BROTLI_PARAM_MODE]: mode, [constants.BROTLI_PARAM_QUALITY]: 2 },
|
|
420
|
+
});
|
|
421
|
+
const BROTLI_TEXT_OPTIONS = brotliParams(constants.BROTLI_MODE_TEXT);
|
|
422
|
+
const BROTLI_GENERIC_OPTIONS = brotliParams(constants.BROTLI_MODE_GENERIC);
|
|
423
|
+
export function brotliOptions(contentType: string) {
|
|
424
|
+
return contentType.includes('json') || contentType.includes('text') ? BROTLI_TEXT_OPTIONS : BROTLI_GENERIC_OPTIONS;
|
|
425
|
+
}
|
|
419
426
|
/**
|
|
420
427
|
* Serialize a response
|
|
421
428
|
* @param responseData
|
|
@@ -428,24 +435,28 @@ export function serialize(responseData, request, responseObject) {
|
|
|
428
435
|
// client itself actually (just) supports gzip/deflate
|
|
429
436
|
let canCompress = COMPRESSION_THRESHOLD && request.headers.asObject?.['accept-encoding']?.includes('br');
|
|
430
437
|
let responseBody;
|
|
438
|
+
let contentType: string;
|
|
431
439
|
if (responseData?.contentType != null && responseData.data != null) {
|
|
432
440
|
// we use this as a special marker for blobs of data that are explicitly one content type
|
|
433
|
-
|
|
441
|
+
contentType = responseData.contentType;
|
|
442
|
+
responseObject.headers.set('Content-Type', contentType);
|
|
434
443
|
responseObject.headers.set('Vary', 'Accept-Encoding');
|
|
435
444
|
responseBody = responseData.data;
|
|
436
445
|
} else if (responseData instanceof Uint8Array || responseData instanceof Blob) {
|
|
437
446
|
// If a user function or property returns a direct Buffer of binary data, this is the most appropriate content
|
|
438
447
|
// type for it.
|
|
439
|
-
|
|
448
|
+
contentType = 'application/octet-stream';
|
|
449
|
+
responseObject.headers.set('Content-Type', contentType);
|
|
440
450
|
responseObject.headers.set('Vary', 'Accept-Encoding');
|
|
441
451
|
responseBody = responseData;
|
|
442
452
|
} else {
|
|
443
453
|
const serializer = findBestSerializer(request);
|
|
444
454
|
if (serializer.serializer.compressible === false) canCompress = false;
|
|
455
|
+
contentType = serializer.type;
|
|
445
456
|
// TODO: If a different content type is preferred, look through resources to see if there is one
|
|
446
457
|
// specifically for that content type (most useful for html).
|
|
447
458
|
responseObject.headers.set('Vary', 'Accept, Accept-Encoding');
|
|
448
|
-
responseObject.headers.set('Content-Type',
|
|
459
|
+
responseObject.headers.set('Content-Type', contentType);
|
|
449
460
|
if (
|
|
450
461
|
typeof responseData === 'object' &&
|
|
451
462
|
responseData &&
|
|
@@ -454,9 +465,9 @@ export function serialize(responseData, request, responseObject) {
|
|
|
454
465
|
) {
|
|
455
466
|
if (
|
|
456
467
|
responseData.mapError &&
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
468
|
+
contentType !== 'application/x-ndjson' &&
|
|
469
|
+
contentType !== 'application/ndjson' &&
|
|
470
|
+
contentType !== 'text/event-stream'
|
|
460
471
|
) {
|
|
461
472
|
// indicate that we want iterator errors to be returned so we can serialize them in a meaningful way, if possible
|
|
462
473
|
const getColumns = responseData.getColumns;
|
|
@@ -468,33 +479,27 @@ export function serialize(responseData, request, responseObject) {
|
|
|
468
479
|
});
|
|
469
480
|
responseData.getColumns = getColumns;
|
|
470
481
|
}
|
|
471
|
-
|
|
472
|
-
if (canCompress)
|
|
482
|
+
const serialized = serializer.serializer.serializeStream(responseData, responseObject, request);
|
|
483
|
+
if (!canCompress) return serialized;
|
|
484
|
+
// a handler may return a complete body (msgpack does for arrays)
|
|
485
|
+
if (typeof serialized === 'string' || serialized instanceof Uint8Array) responseBody = serialized;
|
|
486
|
+
else {
|
|
473
487
|
responseObject.headers.set('Content-Encoding', 'br');
|
|
474
|
-
const
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
? constants.BROTLI_MODE_TEXT
|
|
481
|
-
: constants.BROTLI_MODE_GENERIC,
|
|
482
|
-
[constants.BROTLI_PARAM_QUALITY]: 2, // go fast
|
|
483
|
-
},
|
|
484
|
-
})
|
|
485
|
-
);
|
|
486
|
-
if (uncompressedStream[streamStartup]) stream[streamStartup] = uncompressedStream[streamStartup];
|
|
488
|
+
const source = typeof serialized.pipe === 'function' ? serialized : Readable.from(serialized);
|
|
489
|
+
// pipeline, not pipe: a source error must destroy the compressor (which the HTTP layer reports) rather
|
|
490
|
+
// than be emitted with no listener
|
|
491
|
+
const compressed = stream.pipeline(source, createBrotliCompress(brotliOptions(contentType)), () => {});
|
|
492
|
+
if (source[streamStartup]) compressed[streamStartup] = source[streamStartup];
|
|
493
|
+
return compressed;
|
|
487
494
|
}
|
|
488
|
-
|
|
489
|
-
}
|
|
490
|
-
responseBody = serializer.serializer.serialize(responseData, responseObject);
|
|
495
|
+
} else responseBody = serializer.serializer.serialize(responseData, responseObject);
|
|
491
496
|
}
|
|
492
497
|
if (canCompress && responseBody?.length > COMPRESSION_THRESHOLD) {
|
|
493
498
|
// TODO: Only do this if the size is large and we can cache the result (otherwise use logic above)
|
|
494
499
|
responseObject.headers.set('Content-Encoding', 'br');
|
|
495
500
|
// if we have a single buffer (or string) we compress in a single async call
|
|
496
501
|
return new Promise((resolve, reject) =>
|
|
497
|
-
brotliCompress(responseBody, (err, data) => {
|
|
502
|
+
brotliCompress(responseBody, brotliOptions(contentType), (err, data) => {
|
|
498
503
|
if (err) reject(err);
|
|
499
504
|
else resolve(data);
|
|
500
505
|
})
|
|
@@ -24,7 +24,7 @@ import { UwsRequest, UwsRequestBody } from './Request.ts';
|
|
|
24
24
|
import { Headers } from './Headers.ts';
|
|
25
25
|
import { when } from '../../utility/when.ts';
|
|
26
26
|
import { ClientError } from '../../utility/errors/hdbError.ts';
|
|
27
|
-
import { errorToString } from '../../utility/logging/harper_logger.ts';
|
|
27
|
+
import { errorToString, errorForLog, warn } from '../../utility/logging/harper_logger.ts';
|
|
28
28
|
|
|
29
29
|
// uWS has no npm package; it's installed from a GitHub tag and is platform/ABI-specific.
|
|
30
30
|
// Imported lazily so harper builds/loads on platforms without a uWS binary.
|
|
@@ -323,29 +323,57 @@ function writeResponse(
|
|
|
323
323
|
* Stream a Node Readable to the uWS response with backpressure. uWS only flushes the status/headers
|
|
324
324
|
* on the first body write, so for text/event-stream (SSE) — where the client must see the stream
|
|
325
325
|
* open before any event — we emit a spec-valid comment line to force the flush. Client disconnect
|
|
326
|
-
* (via `signal`)
|
|
327
|
-
*
|
|
326
|
+
* (via `signal`) destroys the source and stops writing (writing to an aborted uWS response is
|
|
327
|
+
* invalid).
|
|
328
|
+
*
|
|
329
|
+
* The response is completed only when the source ends cleanly. A source that errors, or closes
|
|
330
|
+
* without ending, aborts the connection instead: `res.end()` would write the chunked terminator and
|
|
331
|
+
* hand the client a truncated body as a complete response.
|
|
328
332
|
*/
|
|
329
333
|
function streamResponse(
|
|
330
334
|
res: UwsResponse,
|
|
331
335
|
status: number,
|
|
332
336
|
headers: Headers,
|
|
333
|
-
source: {
|
|
337
|
+
source: {
|
|
338
|
+
on: Function;
|
|
339
|
+
once: Function;
|
|
340
|
+
pause: Function;
|
|
341
|
+
resume: Function;
|
|
342
|
+
destroy?: Function;
|
|
343
|
+
destroyed?: boolean;
|
|
344
|
+
errored?: Error | null;
|
|
345
|
+
},
|
|
334
346
|
signal?: AbortSignal
|
|
335
347
|
): void {
|
|
336
348
|
let finished = false;
|
|
337
|
-
const finish = (
|
|
349
|
+
const finish = (sourceEnded: boolean) => {
|
|
338
350
|
if (finished) return;
|
|
339
351
|
finished = true;
|
|
340
352
|
signal?.removeEventListener('abort', onAbort);
|
|
341
|
-
if (
|
|
353
|
+
if (sourceEnded) res.cork(() => res.end());
|
|
354
|
+
else {
|
|
355
|
+
source.destroy?.();
|
|
356
|
+
// runs the onAborted callback, so `finished` has to be set by now
|
|
357
|
+
res.close();
|
|
358
|
+
}
|
|
359
|
+
};
|
|
360
|
+
const fail = (error?: Error | null) => {
|
|
361
|
+
if (finished) return;
|
|
362
|
+
if (error) warn(errorForLog(error));
|
|
363
|
+
finish(false);
|
|
342
364
|
};
|
|
343
365
|
function onAbort() {
|
|
344
366
|
if (finished) return;
|
|
345
367
|
finished = true;
|
|
346
368
|
source.destroy?.();
|
|
347
369
|
}
|
|
370
|
+
// Attached before anything can return: destroy(error) emits on a later tick, and an 'error' with
|
|
371
|
+
// no listener is thrown. Not once(), because a source may emit more than one.
|
|
372
|
+
source.on('error', fail);
|
|
348
373
|
if (signal?.aborted) return onAbort();
|
|
374
|
+
// the handler resolves asynchronously, so a source can be destroyed, and its 'close' already
|
|
375
|
+
// emitted, before this gets to listen for it
|
|
376
|
+
if (source.destroyed) return fail(source.errored);
|
|
349
377
|
signal?.addEventListener('abort', onAbort, { once: true });
|
|
350
378
|
|
|
351
379
|
const isSse = String(headers.get('content-type') ?? '').includes('text/event-stream');
|
|
@@ -371,7 +399,7 @@ function streamResponse(
|
|
|
371
399
|
}
|
|
372
400
|
});
|
|
373
401
|
source.once('end', () => finish(true));
|
|
374
|
-
source.once('
|
|
402
|
+
source.once('close', () => fail());
|
|
375
403
|
}
|
|
376
404
|
|
|
377
405
|
// Normalize uWS's remote-address text (raw IPv6 form, IPv4-mapped for v4 peers) to a readable IP.
|
package/server/threads/itc.js
CHANGED
|
@@ -17,6 +17,7 @@ module.exports = {
|
|
|
17
17
|
SchemaEventMsg,
|
|
18
18
|
};
|
|
19
19
|
let serverItcHandlers;
|
|
20
|
+
const RESTORE_CLOSE_ACK_TIMEOUT_MS = 30000;
|
|
20
21
|
onMessageFromWorkers(async (event, sender) => {
|
|
21
22
|
serverItcHandlers = serverItcHandlers || require('../itc/serverHandlers.js');
|
|
22
23
|
let error;
|
|
@@ -54,6 +55,26 @@ function sendItcEvent(event, includeJobWorkers = false) {
|
|
|
54
55
|
// The main thread's threadId is 0 (worker_threads convention); parentPort.threadId
|
|
55
56
|
// is set to 0 in workers, so sendToThread(0, ...) routes back to main.
|
|
56
57
|
if (event.message) event.message.originator = threadId;
|
|
58
|
+
if (
|
|
59
|
+
event.type === hdbTerms.ITC_EVENT_TYPES.SCHEMA &&
|
|
60
|
+
event.message?.operation === hdbTerms.OPERATIONS_ENUM.RESTORE_BACKUP &&
|
|
61
|
+
event.message.restorePhase
|
|
62
|
+
) {
|
|
63
|
+
// Both restore phases reach job workers, because they write blobs too. Ordinary gossip excludes
|
|
64
|
+
// them to avoid re-entrant waits, but their message handlers stay live while a job's own async
|
|
65
|
+
// work is suspended, so that objection does not apply here -- and fencing them at close while
|
|
66
|
+
// sending the release only to ordinary workers would leave every job thread fenced for the life
|
|
67
|
+
// of the process.
|
|
68
|
+
if (event.message.restorePhase === 'close') {
|
|
69
|
+
// Strict and bounded: the restore must not proceed past a worker that never acknowledged the
|
|
70
|
+
// close, nor wait on a wedged one forever -- unbounded would strand the restore holding its
|
|
71
|
+
// marker, before verifyDatabaseClosed's own deadline could report a clean 409.
|
|
72
|
+
return broadcastWithStrictAcknowledgement(event, RESTORE_CLOSE_ACK_TIMEOUT_MS, true);
|
|
73
|
+
}
|
|
74
|
+
// The release is deliberately best-effort: a restore that has already done its work must not be
|
|
75
|
+
// reported as failed because one worker was slow to take its fence back off.
|
|
76
|
+
return broadcastWithAcknowledgement(event, undefined, false, true);
|
|
77
|
+
}
|
|
57
78
|
return broadcastWithAcknowledgement(event, undefined, false, includeJobWorkers);
|
|
58
79
|
}
|
|
59
80
|
|