@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
package/components/DESIGN.md
CHANGED
|
@@ -106,19 +106,25 @@ did not need it:
|
|
|
106
106
|
activation cannot re-derive. An _optional_ record could not distinguish a payload build from a package
|
|
107
107
|
build whose record was lost, so a missing, malformed or wrong-version one is refused rather than defaulted.
|
|
108
108
|
- **Claiming an id is exclusive.** `buildCandidateApplication` used to tolerate an existing deployment
|
|
109
|
-
directory because a fresh UUID could not collide; a public id can be repeated by an operator
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
as part of the claim
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
109
|
+
directory because a fresh UUID could not collide; a public id can be repeated by an operator, so a claim
|
|
110
|
+
rejects another component's directory and any directory carrying `.complete`, and rebuilds only over an
|
|
111
|
+
uncertified partial of its _own_ component — in a fresh claim, never inside the partial tree. Ownership is
|
|
112
|
+
published as part of the claim, not at certification, because `buildCandidateApplication` can spend minutes
|
|
113
|
+
resolving and packing before any tree exists to infer an owner from. The claim is built at
|
|
114
|
+
`.deploy-staging/.claiming-<uuid>-<digest>`, named there, and renamed onto the id, so the directory only
|
|
115
|
+
ever appears at its id already attributed. It used to be an exclusive `mkdir` followed by the sidecar write,
|
|
116
|
+
and a death between the two left an unattributed directory — indistinguishable from a claim in flight, so
|
|
117
|
+
refused forever. Now no live claim is ever visible unattributed: an EMPTY directory at an id is that
|
|
118
|
+
wreckage from an older build and is taken over (`rmdir`, which removes nothing else); a non-empty
|
|
119
|
+
unattributed one is still refused. A claim that died before its rename names no id, is skipped by every scan
|
|
120
|
+
of the staging root (all of them skip dot-prefixed entries), and is removed under the component's lock by its
|
|
121
|
+
next deploy, or by `drop_component`; a digest of the component in its name, rather than the name itself, makes
|
|
122
|
+
that attributable without a read and cannot push a long name past a filename limit.
|
|
123
|
+
On Windows a rename onto any existing directory fails with the `EPERM` a transient holder raises, so the
|
|
124
|
+
destination is classified before renaming rather than after a spent retry budget. The id this request names
|
|
125
|
+
is also pinned through the preparation preamble, so retention cannot evict the artifact the request is about
|
|
126
|
+
to use — which it otherwise would, immediately, at `deployment_stagingRetention_maxCount: 0`. What an id
|
|
127
|
+
names after its activation is the next section's subject.
|
|
122
128
|
- **Staging owns its bytes.** A `file:<directory>` source is refused, and so is any symlink in the built
|
|
123
129
|
tree resolving outside it (bar the `node_modules/harper`/`harperdb` links the loader owns and repairs).
|
|
124
130
|
Certification fsyncs the tree but follows no links, and the post-swap relocation repair leaves external
|
|
@@ -221,8 +227,9 @@ never depends on it. Roll-forward requires the journal, the candidate and `.comp
|
|
|
221
227
|
observable, which means a lost directory update degrades to a roll back rather than to a wrong decision.
|
|
222
228
|
|
|
223
229
|
Retiring the rollback record only marks the displaced tree disposable; both the activation path and
|
|
224
|
-
recovery then
|
|
225
|
-
|
|
230
|
+
recovery then put it back into its deployment's record, or sweep it when nothing can keep it (see "A deployment
|
|
231
|
+
id keeps naming its release"), since otherwise the components root would grow by a whole component version per
|
|
232
|
+
deploy. The retire is **correctness, not hygiene** — that marker is what stops the legacy pass treating the record as
|
|
226
233
|
authoritative once the journal is gone — so a failure to retire propagates and the component fails closed
|
|
227
234
|
with its journal intact. Only the sweep itself is best-effort, because it costs disk rather than a wrong
|
|
228
235
|
decision. For the same reason, a swap whose rename cannot be confirmed on storage skips both the retire
|
|
@@ -278,9 +285,10 @@ publication-lock timeout is rethrown as a 503 `ServerError` rather than the prep
|
|
|
278
285
|
class, which recovery reads as "a live deploy holds this component's lock" — a deferral, not a verdict.
|
|
279
286
|
|
|
280
287
|
The throw also means `deploy_component` never reaches replication: the release is live on this node only, and
|
|
281
|
-
the error says so. A fresh deploy converges
|
|
282
|
-
|
|
283
|
-
|
|
288
|
+
the error says so. A fresh deploy converges. So does a retry of a `deployment_id` activation: its preamble settles
|
|
289
|
+
the kept journal, publishing the entry, and then answers that the id is already live here, so the operation goes
|
|
290
|
+
on to replicate. Peers of an immediate deploy hold no artifact, so its id answers 404 there. Even so, a condition
|
|
291
|
+
that would fail every publish is refused BEFORE anything moves: `assertRootConfigEffectPublishable`
|
|
284
292
|
runs ahead of the journal and refuses an effect that would have to change a document that does not parse, or
|
|
285
293
|
whose directory this process cannot write. Only a static condition is caught; a publish can still fail.
|
|
286
294
|
|
|
@@ -347,9 +355,9 @@ and validated, activated by nobody. Recovery used to remove every owned journal-
|
|
|
347
355
|
dormant builds and bounds them per component to `deployment_stagingRetention_maxCount` (default 5, 0 keeps
|
|
348
356
|
none), newest by `.complete` mtime, ties broken by deployment id so concurrent passes pick the same victims.
|
|
349
357
|
Everything else journal-less — a partial tree, a directory whose tree already moved live, a stale
|
|
350
|
-
`.unsettled` — is still residue and still removed.
|
|
351
|
-
|
|
352
|
-
|
|
358
|
+
`.unsettled` — is still residue and still removed. Two things produce them: a stage (`activate: false`), and every
|
|
359
|
+
activation that displaces a release it can put back under its own id (next section). A deployment's record — its
|
|
360
|
+
directory without a tree — is not a build and is not counted.
|
|
353
361
|
|
|
354
362
|
Removal is decided **only under the owner's preparation lock**: activation writes `.complete` moments
|
|
355
363
|
before its journal while holding that lock, so an unlocked read of "complete, no journal" is a candidate, not
|
|
@@ -370,6 +378,86 @@ hygiene: it never fails a component closed and never replaces a deploy's own err
|
|
|
370
378
|
best-effort under filesystem failure and is not a storage quota — journaled, unsettled and unowned
|
|
371
379
|
directories are preserved by design and can still fill a volume.
|
|
372
380
|
|
|
381
|
+
### A deployment id keeps naming its release after the swap
|
|
382
|
+
|
|
383
|
+
On a node, a deployment id names its release for as long as the node holds that release: while it is dormant,
|
|
384
|
+
`deployment_id` activates it; while it is live, activating it answers that it already is, without a swap; when a
|
|
385
|
+
later activation displaces it, it becomes dormant again under the same id, within
|
|
386
|
+
`deployment_stagingRetention_maxCount`. A node holding neither answers 404 (#2315 step 5). Four pieces:
|
|
387
|
+
|
|
388
|
+
- **Provenance rides the tree.** `buildCandidateApplication` writes `.harper-deployment.json` — `{ v, component,
|
|
389
|
+
deploymentId }` — at the top of every candidate it extracts, after the install and before `.complete`, replacing
|
|
390
|
+
any marker the payload carried. It moves with the tree through the commit rename, so the live tree always names
|
|
391
|
+
the build that made it live, and anything else that replaces the directory replaces the marker too: it is
|
|
392
|
+
absent, never stale. A `file:` link gets none, since its target is not the deploy's to write, and a live link
|
|
393
|
+
has no provenance whatever its target holds. The tree is the component's own to write, which is why the marker
|
|
394
|
+
carries only an id: nothing published on activation comes from it. A component rewriting its own marker can
|
|
395
|
+
mislabel only its own releases. `package_component` and `get_components` leave the top-level marker out; a
|
|
396
|
+
deeper file of that name is the component's.
|
|
397
|
+
- **The id's record outlives the swap.** A build whose caller declared what it publishes (`describeArtifact` — every
|
|
398
|
+
`deploy_component`) writes `.artifact.json` before `.complete`, as a stage does, and a committed activation
|
|
399
|
+
keeps `.deploy-staging/<id>` minus its tree and journal: `.component`, `.complete`, `.artifact.json`. That
|
|
400
|
+
record is where the release goes back to, so its directory entry is flushed before the swap, as a stage's is. A
|
|
401
|
+
boot install, `add_component` and a link build declare nothing and keep none, as before. Keeping the descriptor
|
|
402
|
+
here rather than snapshotting the entry at displacement is
|
|
403
|
+
deliberate: for a boot install the entry in force is the new build's, so after an out-of-band edit the displaced
|
|
404
|
+
tree would be described with the wrong package. Re-activating an id publishes what that deployment declared,
|
|
405
|
+
which is the contract a staged artifact already had; `set_configuration` edits made while it was live are not
|
|
406
|
+
carried back, just as a package redeploy replaces the entry whole. One side effect, stated: an immediate deploy
|
|
407
|
+
that crashes before its first rename is now DESCRIBED, so recovery returns its candidate to dormant rather than
|
|
408
|
+
discarding it.
|
|
409
|
+
- **The displaced tree goes back into its record.** `retainDisplacedRelease` runs where a committed activation
|
|
410
|
+
disposes of the tree it displaced — after the retire, in `activateCandidateApplication`'s post-commit block, in
|
|
411
|
+
`sweepAsideRecords` on a roll forward, and in the legacy pass for a retired tree still in the aside — and
|
|
412
|
+
renames it into `<record>/<component>` when its marker names this component's record. **Only an explicit
|
|
413
|
+
retention decision deletes a tree**, so its verdict is three-way: _kept_; _ineligible_ (no marker, no usable
|
|
414
|
+
record, or `maxCount: 0`, when the record goes too) — swept as before; or _failed_ (a read error other than
|
|
415
|
+
ENOENT, or a failed rename) — left retired in the aside with its record for the legacy pass to retry at the next
|
|
416
|
+
preparation, since on Windows the rename and the delete both fail while something holds a handle in the tree.
|
|
417
|
+
A failure after the rename (syncing its parents) propagates, as the aside syncs do. At the activation it keeps
|
|
418
|
+
the journal, so recovery repeats the aside barrier before letting it go — not the kept tree's own entry, whose
|
|
419
|
+
loss costs only that kept copy. In recovery it fails the component closed with its journal, the retire's own
|
|
420
|
+
contract. The kept tree's
|
|
421
|
+
`.complete` mtime is refreshed (retried briefly) so retention orders it by when it stopped being live; left at
|
|
422
|
+
its build time, stages nobody activated would outrank it. **Only the activation's own site prunes** after a
|
|
423
|
+
keep, pinning the kept id: settlement runs inside a request's preamble, whose pin it cannot see, and a prune
|
|
424
|
+
there evicted the very artifact that request was activating. The pass a settlement runs inside counts what it
|
|
425
|
+
kept and pins it beside its own pin, so a refresh that failed cannot let that pass evict the release it just put
|
|
426
|
+
back. A kept release is not re-certified: it is kept as
|
|
427
|
+
it was when displaced, including what it wrote into its own directory while live, and activation re-runs the
|
|
428
|
+
link rule and the load validation. An absolute link `repairRelocatedDependencyLinks` wrote after its own swap (a
|
|
429
|
+
Windows `file:`/workspace junction) names the live path, so such a release's re-activation is refused 409.
|
|
430
|
+
- **Activating the live id answers without a swap.** After the preamble, `prepareApplication` in `activate` mode
|
|
431
|
+
compares the live marker with the id: equal means the release is already serving here — a retry whose earlier
|
|
432
|
+
attempt swapped on this node and failed later or elsewhere — so it sets `alreadyActive`, requests a restart
|
|
433
|
+
(nothing can tell whether the running generation ever loaded it, the reasoning a late startup preparation
|
|
434
|
+
follows), and returns; `deploy_component` emits the `prepare` phase end itself, since nothing ran
|
|
435
|
+
`validateCandidate`, and replicates. A restart it performs replaces every worker, dedicated ones included: which
|
|
436
|
+
ones loaded the previous release is unknowable, since the preamble may just have published an isolation change.
|
|
437
|
+
Per node, with no cross-node inference: a node that swapped answers success, one still holding the artifact
|
|
438
|
+
activates it, one with neither answers 404. Only `activate`: a deploy or stage claims an id the origin minted
|
|
439
|
+
fresh, and harper-pro never resends a replicated operation (`sendOperationToNode` is one socket per call), so a
|
|
440
|
+
replicated deploy of an id this node already holds — only a hand-crafted `_deploymentId` produces one — is refused
|
|
441
|
+
by the claim. A record whose release is neither live nor kept answers 404, where a missing tree used to answer 409.
|
|
442
|
+
|
|
443
|
+
**A record is stale only when nothing refers to it.** "Not live" is not enough: a crash after the commit and
|
|
444
|
+
before the re-home leaves the displaced release's record and its tree in `.deploy-aside`, and a boot scan can
|
|
445
|
+
reach the record before the journal that will re-home into it — the record would be deleted and its only tree
|
|
446
|
+
swept. So a record is residue only when neither the live marker nor the marker of any tree in the owner's aside,
|
|
447
|
+
retired or not, names it. Boot recovery checks that unlocked and leaves a referenced record alone without the
|
|
448
|
+
lock, as it does a dormant build (#2531: a per-directory lock on every pass makes healthy components lose the
|
|
449
|
+
250 ms probe to sibling threads), then re-derives it under the owner's lock before removing a stale one.
|
|
450
|
+
`drop_component` removes the dropped component's records and abandoned claims along with its dormant builds.
|
|
451
|
+
|
|
452
|
+
Compatibility: a tree made live before this change has no marker, so the first deploy after the upgrade sweeps
|
|
453
|
+
it. An older build reads a record as residue and removes it, and a kept release as an ordinary dormant build it
|
|
454
|
+
can activate; the marker is an ignored file to it. The journal format is unchanged. A peer on an older build
|
|
455
|
+
answers 404 to a consumed id, as before. `deployment_stagingRetention_maxCount` (default 5) now bounds kept
|
|
456
|
+
releases with staged builds, and each is a whole installed tree, so at the default a component holds up to five
|
|
457
|
+
extra copies of itself; `0` keeps no previous release, and the rest of this section still applies. Until #2315
|
|
458
|
+
step 7, a package component reinstalls from its source on the first start after each package deploy; that
|
|
459
|
+
reinstall keeps no record, but the certified tree it displaces is kept, which is the one worth keeping.
|
|
460
|
+
|
|
373
461
|
## Component preparation is serialized across worker threads
|
|
374
462
|
|
|
375
463
|
`prepareApplication()` performs one transaction per component: build the replacement, validate it, then swap it in (see "A deploy builds off to the side" below). Deploy operations can execute on worker threads as well as main, so a module-local promise queue is insufficient—each worker has its own module registry. `withComponentPreparationLock()` (`components/componentPreparationLock.ts`) instead acquires an atomic filesystem lock keyed by the absolute component path. The deprecated `install_node_modules` operation uses the same lock, so it cannot run npm concurrently with a deploy.
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import { basename } from 'node:path';
|
|
2
|
+
|
|
3
|
+
/** Rides the rename that makes a tree live, so it is never stale. Only an id: the tree is the component's to write. */
|
|
4
|
+
export const DEPLOYMENT_PROVENANCE_FILE = '.harper-deployment.json';
|
|
5
|
+
|
|
6
|
+
const DEPLOYMENT_PROVENANCE_VERSION = 1;
|
|
7
|
+
|
|
8
|
+
export function formatDeploymentProvenance(component: string, deploymentId: string): string {
|
|
9
|
+
return JSON.stringify({ v: DEPLOYMENT_PROVENANCE_VERSION, component, deploymentId });
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
export function parseDeploymentProvenance(raw: string, component: string): string | undefined {
|
|
13
|
+
let parsed: any;
|
|
14
|
+
try {
|
|
15
|
+
parsed = JSON.parse(raw);
|
|
16
|
+
} catch {
|
|
17
|
+
return undefined;
|
|
18
|
+
}
|
|
19
|
+
if (parsed?.v !== DEPLOYMENT_PROVENANCE_VERSION || parsed.component !== component) return undefined;
|
|
20
|
+
const deploymentId = parsed.deploymentId;
|
|
21
|
+
// A single path segment that cannot name a claim in progress or any other dot-prefixed staging entry.
|
|
22
|
+
return typeof deploymentId === 'string' &&
|
|
23
|
+
deploymentId.length > 0 &&
|
|
24
|
+
deploymentId === basename(deploymentId) &&
|
|
25
|
+
!deploymentId.startsWith('.')
|
|
26
|
+
? deploymentId
|
|
27
|
+
: undefined;
|
|
28
|
+
}
|
package/components/operations.js
CHANGED
|
@@ -39,6 +39,7 @@ const {
|
|
|
39
39
|
retireComponentDirectory,
|
|
40
40
|
} = require('./Application.ts');
|
|
41
41
|
const { COMPONENT_PREPARATION_LOCK_DIR, withComponentPreparationLock } = require('./componentPreparationLock.ts');
|
|
42
|
+
const { DEPLOYMENT_PROVENANCE_FILE } = require('./deploymentProvenance.ts');
|
|
42
43
|
const {
|
|
43
44
|
applyRootConfigEffect,
|
|
44
45
|
assertRootConfigEffectPublishable,
|
|
@@ -888,6 +889,8 @@ async function deployComponent(req) {
|
|
|
888
889
|
await validateComponentLoads(candidateDirPath, emit);
|
|
889
890
|
},
|
|
890
891
|
});
|
|
892
|
+
// Nothing ran `validateCandidate`, which ends this phase.
|
|
893
|
+
if (application.alreadyActive) emit('phase', { phase: 'prepare', status: 'done' });
|
|
891
894
|
// The build is certified on disk from here on, so every later failure — a peer result, or a rejection
|
|
892
895
|
// thrown by the replication layer itself — still leaves an artifact this id can activate.
|
|
893
896
|
if (mode === 'stage') stagedOnOrigin = true;
|
|
@@ -938,8 +941,9 @@ async function deployComponent(req) {
|
|
|
938
941
|
}
|
|
939
942
|
// A still-isolated application restarts only its own dedicated worker. Everything else restarts the
|
|
940
943
|
// pool: a shared application, and either direction of an isolation flip, where the reconcile in
|
|
941
|
-
// restartWorkers starts or stops the moving application's own worker.
|
|
942
|
-
|
|
944
|
+
// restartWorkers starts or stops the moving application's own worker. An already-live retry cannot know
|
|
945
|
+
// which workers loaded the previous release, so it restarts them all.
|
|
946
|
+
const restartScope = application.alreadyActive ? '*' : wasIsolated && nowIsolated ? application.name : undefined;
|
|
943
947
|
if (mode === 'stage') {
|
|
944
948
|
// No restart and no restart-required flag: nothing about the running component changed. The
|
|
945
949
|
// marker is what tells the origin which peers understood the request — see the confirmation
|
|
@@ -967,7 +971,8 @@ async function deployComponent(req) {
|
|
|
967
971
|
operation: 'restart_service',
|
|
968
972
|
service: 'http',
|
|
969
973
|
scope: manageThreads.encodeRestartScope(restartScope),
|
|
970
|
-
scopeFallback:
|
|
974
|
+
scopeFallback:
|
|
975
|
+
restartScope === undefined || restartScope === '*' ? undefined : manageThreads.encodeRestartScope(undefined),
|
|
971
976
|
replicated: true,
|
|
972
977
|
});
|
|
973
978
|
emit('phase', { phase: 'restart', status: 'done' });
|
|
@@ -1204,6 +1209,7 @@ async function getComponents() {
|
|
|
1204
1209
|
// Recursive function that will traverse the components dir and build json
|
|
1205
1210
|
// directory tree as it goes.
|
|
1206
1211
|
const rootConfig = configUtils.getConfiguration();
|
|
1212
|
+
const componentsRoot = configUtils.getConfigPath(hdbTerms.CONFIG_PARAMS.COMPONENTSROOT);
|
|
1207
1213
|
const walkDir = async (dir, result) => {
|
|
1208
1214
|
try {
|
|
1209
1215
|
const list = await fs.readdir(dir, { withFileTypes: true });
|
|
@@ -1211,12 +1217,13 @@ async function getComponents() {
|
|
|
1211
1217
|
const itemName = item.name;
|
|
1212
1218
|
// Deny-list, not a dot-prefix skip: component CONTENTS legitimately include dot-files
|
|
1213
1219
|
// (`.aiignore`, `.env.example`) that callers expect to see, so only Harper's own
|
|
1214
|
-
// bookkeeping
|
|
1220
|
+
// bookkeeping is excluded by name: its directories, and each component's provenance marker.
|
|
1215
1221
|
if (
|
|
1216
1222
|
itemName === 'node_modules' ||
|
|
1217
1223
|
itemName === ASIDE_STAGING_DIR ||
|
|
1218
1224
|
itemName === DEPLOY_STAGING_DIR ||
|
|
1219
|
-
itemName === COMPONENT_PREPARATION_LOCK_DIR
|
|
1225
|
+
itemName === COMPONENT_PREPARATION_LOCK_DIR ||
|
|
1226
|
+
(itemName === DEPLOYMENT_PROVENANCE_FILE && path.dirname(dir) === componentsRoot)
|
|
1220
1227
|
)
|
|
1221
1228
|
continue;
|
|
1222
1229
|
const itemPath = path.join(dir, itemName);
|
|
@@ -1248,8 +1255,8 @@ async function getComponents() {
|
|
|
1248
1255
|
}
|
|
1249
1256
|
};
|
|
1250
1257
|
|
|
1251
|
-
const results = await walkDir(
|
|
1252
|
-
name:
|
|
1258
|
+
const results = await walkDir(componentsRoot, {
|
|
1259
|
+
name: componentsRoot.split(path.sep).slice(-1).pop(),
|
|
1253
1260
|
entries: [],
|
|
1254
1261
|
});
|
|
1255
1262
|
const { getUnsatisfiedEnv } = require('./componentSecrets.ts');
|
|
@@ -3,6 +3,7 @@ import { stat, readdir } from 'node:fs/promises';
|
|
|
3
3
|
import { Readable, pipeline } from 'node:stream';
|
|
4
4
|
import tar from 'tar-fs';
|
|
5
5
|
import { createGzip } from 'node:zlib';
|
|
6
|
+
import { DEPLOYMENT_PROVENANCE_FILE } from './deploymentProvenance.ts';
|
|
6
7
|
|
|
7
8
|
interface PackageOptions {
|
|
8
9
|
skip_node_modules?: boolean;
|
|
@@ -42,9 +43,12 @@ const INSTALL_OWNED_PATH = join('node_modules', 'harper');
|
|
|
42
43
|
* excluded the whole tree. Shared by the stream packer and the directory walk so they cannot diverge.
|
|
43
44
|
*/
|
|
44
45
|
function isExcluded(directory: string, fullPath: string, options: PackageOptions): boolean {
|
|
46
|
+
const name = basename(fullPath);
|
|
45
47
|
// Unconditional, including under `skip_symlinks`: packed literally, the link ships an absolute path to
|
|
46
48
|
// the packaging host's install root, which arrives dangling.
|
|
47
|
-
if (
|
|
49
|
+
if (name === 'harper' && relative(directory, fullPath) === INSTALL_OWNED_PATH) return true;
|
|
50
|
+
// A package made from a live tree would otherwise carry that release's deployment id into the next one.
|
|
51
|
+
if (name === DEPLOYMENT_PROVENANCE_FILE && relative(directory, fullPath) === DEPLOYMENT_PROVENANCE_FILE) return true;
|
|
48
52
|
if (!options.skip_node_modules) return false;
|
|
49
53
|
const rel = relative(directory, fullPath);
|
|
50
54
|
return rel.split(sep).includes('node_modules') || rel.includes(WEBPACK_CACHE_SEGMENT);
|
package/config-root.schema.json
CHANGED
|
@@ -25,7 +25,7 @@
|
|
|
25
25
|
"compressionThreshold": {
|
|
26
26
|
"type": "integer",
|
|
27
27
|
"minimum": 0,
|
|
28
|
-
"description": "Responses larger than this threshold (bytes) will be compressed for clients that accept
|
|
28
|
+
"description": "Responses larger than this threshold (bytes) will be compressed for clients that accept brotli. Default: 0 (compression disabled)"
|
|
29
29
|
},
|
|
30
30
|
"cors": { "type": "boolean", "description": "Enable Cross Origin Resource Sharing. Default: true" },
|
|
31
31
|
"corsAccessList": {
|
package/dataLayer/DESIGN.md
CHANGED
|
@@ -89,13 +89,41 @@ Three non-obvious mechanics keep that safe:
|
|
|
89
89
|
it and broadcasting a reload would surface the earlier attempt's partial/corrupt directory as
|
|
90
90
|
healthy. Only a _fresh_ marker on a _previously healthy_ database that failed before destruction is
|
|
91
91
|
safe to clear.
|
|
92
|
-
- **The ITC close broadcast is best-effort, so closure is verified before the purge.**
|
|
93
|
-
broadcast (`signalSchemaChange`) resolves after remote handlers complete but times
|
|
94
|
-
"best-effort"
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
92
|
+
- **The ITC close broadcast is normally best-effort, so closure is verified before the purge.** A
|
|
93
|
+
SCHEMA broadcast (`signalSchemaChange`) usually resolves after remote handlers complete but times
|
|
94
|
+
out at 30s "best-effort" and swallows errors. The restore `close` phase is stricter: it waits until
|
|
95
|
+
every eligible recipient acknowledges or its port closes, and aborts the restore with a retryable
|
|
96
|
+
409 if that does not happen within 30s, because proceeding past an unconfirmed blob-save barrier
|
|
97
|
+
would re-open the race the barrier exists to close. A destructive purge still
|
|
98
|
+
verifies closure independently: `restoreBackup` polls rocksdb-js `registryStatus()` (process-global
|
|
99
|
+
across worker threads) until the database path has no open instance, and aborts with a 409 —
|
|
100
|
+
_cleaning up the marker, since nothing was destroyed_ — if handles remain.
|
|
101
|
+
- **The close acknowledgement fences blob saves, deferred reclamation and orphan cleanup, not just
|
|
102
|
+
database handles.** A store
|
|
103
|
+
handle can close while a `saveBlob` file pipeline it started is still pending, because blob roots
|
|
104
|
+
live outside RocksDB and streamed saves settle independently of the record write; a queued
|
|
105
|
+
reclamation is worse still, being a timer that never consults the database at all. The restore
|
|
106
|
+
signal therefore has explicit `close` and `reload` phases: every worker refuses new saves for that
|
|
107
|
+
database, stops draining reclamations for it, stops any `cleanup_orphan_blobs` walk in progress
|
|
108
|
+
(its "nothing references this path" verdict was reached against the generation being replaced),
|
|
109
|
+
awaits the saves and unlinks already dispatched -- including a save-failure cleanup's -- then
|
|
110
|
+
closes the store and acknowledges. Restore signals are the one schema broadcast that includes job
|
|
111
|
+
workers; ordinary gossip still excludes them to avoid re-entrant broadcast deadlocks.
|
|
112
|
+
|
|
113
|
+
The fence records the token of the restore that currently owns it, not a flag and not a set. Two
|
|
114
|
+
restores of the same database can overlap — a restore releases its lock before awaiting its `reload`
|
|
115
|
+
broadcast — so a database-wide boolean lets the first restore's late reload lift the second one's
|
|
116
|
+
fence, admitting saves during the second one's post-close check. A later close takes ownership
|
|
117
|
+
without the database ever unfencing in between, and a release whose token is not the current owner
|
|
118
|
+
does nothing. A _set_ of tokens fails the opposite way: a restore that dies after destruction never
|
|
119
|
+
reloads, so its token would keep the database fenced for the life of the process, defeating the
|
|
120
|
+
rerun the failure tells the operator to perform.
|
|
121
|
+
|
|
122
|
+
Releasing the fence discards that database's queued reclamations rather than resuming them: they
|
|
123
|
+
condemn file paths belonging to the generation the restore just replaced, so draining them would
|
|
124
|
+
unlink the bytes the restore wrote at those same paths. A restore that aborted before destroying
|
|
125
|
+
anything says so, and its queue is kept and rewoken instead.
|
|
126
|
+
|
|
99
127
|
- **Online restore is impossible for a database a component holds open — and that failure is
|
|
100
128
|
correct.** rocksdb-js's registry is process-global but records only a per-path refCount, with no
|
|
101
129
|
attribution to a thread or component; Harper keeps no component→database ownership map. So when a
|
package/dataLayer/blobBackup.ts
CHANGED
|
@@ -101,6 +101,25 @@ const BACKUP_MARKER_REASONS: CaptureMarkerReasons = {
|
|
|
101
101
|
pending: 'blob was not yet complete when this backup was taken',
|
|
102
102
|
};
|
|
103
103
|
|
|
104
|
+
export async function* walkBlobFiles(root: string): AsyncGenerator<string> {
|
|
105
|
+
const stack: string[] = [root];
|
|
106
|
+
while (stack.length > 0) {
|
|
107
|
+
const dir = stack.pop() as string;
|
|
108
|
+
let entries;
|
|
109
|
+
try {
|
|
110
|
+
entries = await readdir(dir, { withFileTypes: true });
|
|
111
|
+
} catch (error: any) {
|
|
112
|
+
if (error.code === 'ENOENT') continue;
|
|
113
|
+
throw error;
|
|
114
|
+
}
|
|
115
|
+
for (const entry of entries) {
|
|
116
|
+
const path = join(dir, entry.name);
|
|
117
|
+
if (entry.isDirectory()) stack.push(path);
|
|
118
|
+
else if (entry.isFile()) yield path;
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
|
|
104
123
|
/** Put one blob file into the destination, returning how the entry was captured. */
|
|
105
124
|
async function captureBlobFile(
|
|
106
125
|
srcPath: string,
|
|
@@ -151,34 +170,16 @@ export async function copyTree(
|
|
|
151
170
|
onProgress?: () => void
|
|
152
171
|
): Promise<{ substituted: number; captured: number; copied: number }> {
|
|
153
172
|
const counts = { substituted: 0, captured: 0, copied: 0 };
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
if (error.code === 'ENOENT') continue; // directory removed mid-walk
|
|
163
|
-
throw error;
|
|
164
|
-
}
|
|
165
|
-
for (const entry of entries) {
|
|
166
|
-
const srcPath = join(dir, entry.name);
|
|
167
|
-
if (entry.isDirectory()) {
|
|
168
|
-
stack.push(srcPath);
|
|
169
|
-
} else if (entry.isFile()) {
|
|
170
|
-
const destPath = join(destRoot, relative(srcRoot, srcPath));
|
|
171
|
-
if (classify) {
|
|
172
|
-
const disposition = await captureBlobFile(srcPath, destPath, reasons, counts);
|
|
173
|
-
if (disposition === 'pending' || disposition === 'gone') counts.substituted++;
|
|
174
|
-
else if (disposition === 'capture') counts.captured++;
|
|
175
|
-
} else {
|
|
176
|
-
await linkOrCopy(srcPath, destPath, counts);
|
|
177
|
-
}
|
|
178
|
-
onProgress?.();
|
|
179
|
-
}
|
|
180
|
-
// symlinks/other node types in a blob root are not expected and are intentionally skipped
|
|
173
|
+
for await (const srcPath of walkBlobFiles(srcRoot)) {
|
|
174
|
+
const destPath = join(destRoot, relative(srcRoot, srcPath));
|
|
175
|
+
if (classify) {
|
|
176
|
+
const disposition = await captureBlobFile(srcPath, destPath, reasons, counts);
|
|
177
|
+
if (disposition === 'pending' || disposition === 'gone') counts.substituted++;
|
|
178
|
+
else if (disposition === 'capture') counts.captured++;
|
|
179
|
+
} else {
|
|
180
|
+
await linkOrCopy(srcPath, destPath, counts);
|
|
181
181
|
}
|
|
182
|
+
onProgress?.();
|
|
182
183
|
}
|
|
183
184
|
return counts;
|
|
184
185
|
}
|
|
@@ -335,6 +336,44 @@ export async function assertBlobSnapshotRestorable(
|
|
|
335
336
|
}
|
|
336
337
|
}
|
|
337
338
|
|
|
339
|
+
/**
|
|
340
|
+
* Refuse an engine-only restore unless the operator has accepted the mixed result.
|
|
341
|
+
*
|
|
342
|
+
* Restoring engine files without the matching blobs produces a mixed generation: records rolled back
|
|
343
|
+
* to the backup point, addressing whichever blobs the roots happen to hold. There are two ways that
|
|
344
|
+
* goes wrong, and the destination being empty rules out only the first:
|
|
345
|
+
*
|
|
346
|
+
* 1. Blobs are present now, so a restored record can resolve onto a blob that no longer belongs to it,
|
|
347
|
+
* or onto nothing at all if the blob it referenced was deleted since the backup point.
|
|
348
|
+
* 2. The roots are empty, so nothing collides today -- but blob ids are a per-database counter that
|
|
349
|
+
* `getNextFileId` (`resources/blob.ts`) re-seeds by scanning the roots. Empty roots re-seed at 1,
|
|
350
|
+
* which is exactly the id space the restored records already reference, so the *next* blob written
|
|
351
|
+
* is placed at a path a restored record points at. A brand-new target database is not exempt: its
|
|
352
|
+
* roots are empty for the same reason.
|
|
353
|
+
*
|
|
354
|
+
* Purging the roots instead is not the answer -- that strips blobs the restored records still
|
|
355
|
+
* reference -- so the operator has to choose, and the choice is recorded. Case 2 would stop being a
|
|
356
|
+
* hazard if the blob-id high-water mark were persisted with the engine and restored alongside it, so
|
|
357
|
+
* the counter could not fall behind the references it has already handed out.
|
|
358
|
+
*/
|
|
359
|
+
export function assertEngineOnlyRestoreAllowed(
|
|
360
|
+
databaseName: string,
|
|
361
|
+
{ backupHasBlobs, allowEngineOnly }: { backupHasBlobs: boolean; allowEngineOnly: boolean }
|
|
362
|
+
): void {
|
|
363
|
+
// Strictly true, not merely truthy: a manifest is on-disk data, and a corrupt `blobs` value must not
|
|
364
|
+
// be read as "this backup has blobs" and waved past the guard.
|
|
365
|
+
if (backupHasBlobs === true || allowEngineOnly === true) return;
|
|
366
|
+
// Decided from the manifest alone. Walking the destination to tailor the message would turn a
|
|
367
|
+
// deterministic 400 into a traversal that can be slow or fail outright (EACCES on an unreadable
|
|
368
|
+
// root), for wording -- and both hazards are worth stating anyway, since the operator who passes
|
|
369
|
+
// the opt-in is accepting whichever one applies.
|
|
370
|
+
throw new ClientError(
|
|
371
|
+
`Cannot restore an engine-only backup over database '${databaseName}': the backup captured no blobs, so the restored records would address whichever blobs are on disk now. ` +
|
|
372
|
+
`If the database still has blob files, a record can resolve to a blob that no longer belongs to it; if it has none, blob ids restart at 1 and the next blob written lands on a path a restored record already references — so a new target database is not an escape. ` +
|
|
373
|
+
`Restore a backup that includes blobs, or pass 'allow_engine_only' to accept the mixed result.`
|
|
374
|
+
);
|
|
375
|
+
}
|
|
376
|
+
|
|
338
377
|
/**
|
|
339
378
|
* Restore a backup's blob snapshot back into the database's blob roots. Each root is purged and
|
|
340
379
|
* rewritten from `blobs/<backupId>/<rootIndex>/` so the restored blob set matches the backup exactly
|
|
@@ -342,9 +381,11 @@ export async function assertBlobSnapshotRestorable(
|
|
|
342
381
|
*
|
|
343
382
|
* A backup created with blobs excluded (or an older backup that predates blob snapshots) has no
|
|
344
383
|
* snapshot directory: in that case the live blob roots are left untouched and a warning is logged,
|
|
345
|
-
* since purging them would strip blobs the restored records may still reference.
|
|
346
|
-
*
|
|
347
|
-
* `
|
|
384
|
+
* since purging them would strip blobs the restored records may still reference. A restore only
|
|
385
|
+
* reaches that state when the operator accepted it with `allow_engine_only`
|
|
386
|
+
* (`assertEngineOnlyRestoreAllowed` refuses otherwise, whatever the destination roots hold). Roots are restored by index into the *same* configured root;
|
|
387
|
+
* an incompatible root count is rejected up front (see `assertBlobSnapshotRestorable`) rather than
|
|
388
|
+
* collapsed, so blobs are never mis-addressed.
|
|
348
389
|
*/
|
|
349
390
|
export async function restoreBlobSnapshot(
|
|
350
391
|
backupDir: string,
|
|
@@ -56,6 +56,7 @@ export type SearchByConditionsRequest = Query &
|
|
|
56
56
|
database?: string;
|
|
57
57
|
table: string;
|
|
58
58
|
get_attributes: Select;
|
|
59
|
+
hdb_user?: Context['user'];
|
|
59
60
|
reverse?: boolean;
|
|
60
61
|
operator?: Operator;
|
|
61
62
|
};
|
|
@@ -85,6 +86,12 @@ export class ResourceBridge extends BridgeMethods {
|
|
|
85
86
|
attribute: c.attribute ?? c.search_attribute,
|
|
86
87
|
comparator: c.comparator ?? c.search_type,
|
|
87
88
|
value: c.value !== undefined ? c.value : c.search_value, // null is valid value
|
|
89
|
+
...(c.fields === undefined ? null : { fields: c.fields }),
|
|
90
|
+
...(c.includeHighlights === undefined ? null : { includeHighlights: c.includeHighlights }),
|
|
91
|
+
...(c.maxIndexLagMilliseconds === undefined ? null : { maxIndexLagMilliseconds: c.maxIndexLagMilliseconds }),
|
|
92
|
+
...(c.waitForIndexMilliseconds === undefined
|
|
93
|
+
? null
|
|
94
|
+
: { waitForIndexMilliseconds: c.waitForIndexMilliseconds }),
|
|
88
95
|
};
|
|
89
96
|
}
|
|
90
97
|
}
|
|
@@ -107,6 +114,7 @@ export class ResourceBridge extends BridgeMethods {
|
|
|
107
114
|
allowFullScan: true, // operations API can do full scans by default, but REST is more cautious about what it allows
|
|
108
115
|
} as any,
|
|
109
116
|
{
|
|
117
|
+
user: searchObject.hdb_user,
|
|
110
118
|
onlyIfCached: searchObject.onlyIfCached,
|
|
111
119
|
noCacheStore: searchObject.noCacheStore,
|
|
112
120
|
noCache: searchObject.noCache,
|
|
@@ -727,8 +735,14 @@ export class ResourceBridge extends BridgeMethods {
|
|
|
727
735
|
function getSelect({ get_attributes }: any, table: any) {
|
|
728
736
|
if (get_attributes) {
|
|
729
737
|
if (get_attributes[0] === '*') {
|
|
730
|
-
|
|
731
|
-
|
|
738
|
+
const metadata = get_attributes.slice(1).filter((name) => name === '$score' || name === '$highlights');
|
|
739
|
+
if (table.schemaDefined && metadata.length === 0) return;
|
|
740
|
+
get_attributes = [
|
|
741
|
+
...table.attributes
|
|
742
|
+
.filter((attribute) => !attribute.computed && !attribute.relationship)
|
|
743
|
+
.map((attribute) => attribute.name),
|
|
744
|
+
...metadata,
|
|
745
|
+
];
|
|
732
746
|
}
|
|
733
747
|
get_attributes.forceNulls = true;
|
|
734
748
|
return get_attributes;
|