@harperfast/harper 5.2.5 → 5.2.7
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/restart.ts +48 -23
- package/bin/restartExitWatchdog.ts +99 -0
- package/bin/run.ts +16 -10
- package/dist/bin/restart.js +39 -21
- package/dist/bin/restart.js.map +1 -1
- package/dist/bin/restartExitWatchdog.d.ts +5 -0
- package/dist/bin/restartExitWatchdog.js +100 -0
- package/dist/bin/restartExitWatchdog.js.map +1 -0
- package/dist/bin/run.d.ts +2 -0
- package/dist/bin/run.js +17 -10
- package/dist/bin/run.js.map +1 -1
- package/dist/resources/DatabaseTransaction.d.ts +18 -1
- package/dist/resources/DatabaseTransaction.js +63 -1
- package/dist/resources/DatabaseTransaction.js.map +1 -1
- package/dist/resources/LMDBTransaction.d.ts +2 -0
- package/dist/resources/LMDBTransaction.js +7 -1
- package/dist/resources/LMDBTransaction.js.map +1 -1
- package/dist/resources/PrimaryRocksDatabase.js +2 -3
- package/dist/resources/PrimaryRocksDatabase.js.map +1 -1
- package/dist/resources/RecordEncoder.d.ts +16 -0
- package/dist/resources/RecordEncoder.js +93 -3
- package/dist/resources/RecordEncoder.js.map +1 -1
- package/dist/resources/Resource.js +5 -4
- package/dist/resources/Resource.js.map +1 -1
- package/dist/resources/ResourceInterface.d.ts +5 -0
- package/dist/resources/ResourceInterface.js.map +1 -1
- package/dist/resources/Table.js +106 -9
- package/dist/resources/Table.js.map +1 -1
- package/dist/resources/graphql.js +1 -1
- package/dist/resources/graphql.js.map +1 -1
- package/dist/resources/tracked.js +5 -0
- package/dist/resources/tracked.js.map +1 -1
- package/dist/resources/transaction.js +1 -1
- package/dist/resources/transaction.js.map +1 -1
- package/dist/server/threads/manageThreads.d.ts +1 -0
- package/dist/server/threads/manageThreads.js +20 -3
- package/dist/server/threads/manageThreads.js.map +1 -1
- package/dist/utility/processManagement/processManagement.js +21 -3
- package/dist/utility/processManagement/processManagement.js.map +1 -1
- package/npm-shrinkwrap.json +6 -6
- package/package.json +2 -2
- package/resources/DESIGN.md +24 -19
- package/resources/DatabaseTransaction.ts +63 -2
- package/resources/LMDBTransaction.ts +7 -1
- package/resources/PrimaryRocksDatabase.ts +3 -3
- package/resources/RecordEncoder.ts +85 -3
- package/resources/Resource.ts +6 -5
- package/resources/ResourceInterface.ts +5 -0
- package/resources/Table.ts +117 -9
- package/resources/graphql.ts +1 -1
- package/resources/tracked.ts +4 -0
- package/resources/transaction.ts +2 -2
- package/server/DESIGN.md +6 -0
- package/server/threads/manageThreads.js +17 -3
- package/studio/web/assets/{Chat-Lgj1d-8d.js → Chat-etJeJv4a.js} +71 -95
- package/studio/web/assets/FloatingChat-BvmHsuVo.js +23 -0
- package/studio/web/assets/abnfDiagram-VCTEODGH-C0_BAZyO.js +1 -0
- package/studio/web/assets/{alertDialog-BaBrZQ4M.js → alertDialog-DIHt7Z0r.js} +1 -1
- package/studio/web/assets/apiToken-Bl5LC8EH.js +1 -0
- package/studio/web/assets/applications-Dta6Y0Rg.js +296 -0
- package/studio/web/assets/arc-CVRByUzA.js +1 -0
- package/studio/web/assets/architecture-7GRP2DOG-DNdx5tEU.js +1 -0
- package/studio/web/assets/architectureDiagram-5GKGNRK7-BWzrASgm.js +36 -0
- package/studio/web/assets/authStore-qKmCZcaf.js +3 -0
- package/studio/web/assets/blockDiagram-NRAW4CY4-BdJX9Khj.js +129 -0
- package/studio/web/assets/{button-DfBmT4rc.js → button-DhiX-njv.js} +1 -1
- package/studio/web/assets/c4Diagram-UCG6FXSJ-CI6MzGmQ.js +38 -0
- package/studio/web/assets/channel-DtCV8PTL.js +1 -0
- package/studio/web/assets/chevron-up-Df2c8uoU.js +1 -0
- package/studio/web/assets/chunk-75Z2AOVW-BT8tVmks.js +2 -0
- package/studio/web/assets/chunk-DU6HZSFF-9kAOOmI6.js +127 -0
- package/studio/web/assets/{chunk-2GRJ4B5K-DtwG__pv.js → chunk-F27PBJKO-BW7ao8AY.js} +1 -1
- package/studio/web/assets/{chunk-KEIR6QF5-BICK3FdT.js → chunk-FOHPRMQF-DHwB1DNv.js} +2 -2
- package/studio/web/assets/{chunk-5RXB4S5H-D3c7okHC.js → chunk-G27WJ6UU-COyLMcgK.js} +2 -2
- package/studio/web/assets/chunk-GMAD6QVW-BNyXpoQO.js +72 -0
- package/studio/web/assets/chunk-JQ64N6SF-Cyz1IeLf.js +1 -0
- package/studio/web/assets/{chunk-GF5L2VYU-DreJ7Snz.js → chunk-LCL6LL3I-HOzK_ppE.js} +3 -3
- package/studio/web/assets/chunk-OBVCFTLP-D4wWOqDQ.js +1 -0
- package/studio/web/assets/chunk-P2QGCYS3-DmIFY4d7.js +1 -0
- package/studio/web/assets/{chunk-KBJHAD2P-CLDPbAA-.js → chunk-POPQ4Y6H-BPrvMyKz.js} +1 -1
- package/studio/web/assets/{chunk-4I5QYGJK-D1FhvtQq.js → chunk-PWAF6VOD-2zB6IW9i.js} +1 -1
- package/studio/web/assets/chunk-R7TYR2AO-Irip67yr.js +10 -0
- package/studio/web/assets/chunk-RHFEMEQ7-2FgyI8YU.js +168 -0
- package/studio/web/assets/{chunk-6Q2QTUOP-QviMM6bR.js → chunk-SVP7TREG-FwtbH2QC.js} +1 -1
- package/studio/web/assets/chunk-TEH6E4GO-P87k5mNi.js +62 -0
- package/studio/web/assets/{chunk-XXDRQBXY-CaP_jixK.js → chunk-XXDRQBXY-BoGZu3HH.js} +1 -1
- package/studio/web/assets/classDiagram-DTDB5LWJ-DbO_dCNE.js +1 -0
- package/studio/web/assets/classDiagram-v2-JRS7N3AN-DbO_dCNE.js +1 -0
- package/studio/web/assets/{cose-bilkent-JH36ORCC-BVTNhTKD.js → cose-bilkent-JH36ORCC-Cuuf67Bh.js} +1 -1
- package/studio/web/assets/{createLucideIcon-BMkDbMrz.js → createLucideIcon-BKGPfjm2.js} +1 -1
- package/studio/web/assets/cssMode-IAbO_2KK.js +1 -0
- package/studio/web/assets/cynefin-OW5HDTMX-BRkpLFQV.js +1 -0
- package/studio/web/assets/cynefinDiagram-5FMLGOSQ-CHT1DaX6.js +62 -0
- package/studio/web/assets/cytoscape.esm-C2x7inlt.js +321 -0
- package/studio/web/assets/dagre-3AP2YEHR-DpUXBh63.js +4 -0
- package/studio/web/assets/dagre-PrKaheQc.js +1 -0
- package/studio/web/assets/diagram-S7CK7UJ4-BuymVFZT.js +30 -0
- package/studio/web/assets/diagram-UQ7AKVKN-CyP148RM.js +41 -0
- package/studio/web/assets/diagram-VSXAHHWV-CoCAg3M9.js +3 -0
- package/studio/web/assets/diagram-VX7I27RA-BpOqCFca.js +24 -0
- package/studio/web/assets/diagram-Z3DM3KII-Bfpw7Vbj.js +24 -0
- package/studio/web/assets/dialog-CBf0Mr1d.js +1 -0
- package/studio/web/assets/{dist-CeoU7cfW.js → dist-kYfyPZP9.js} +1 -1
- package/studio/web/assets/{dist-cY0BPcz0.js → dist-lkA3O3eM.js} +1 -1
- package/studio/web/assets/download-BtTOBem-.js +1 -0
- package/studio/web/assets/ebnfDiagram-PWID7BFC-DS_6aWqL.js +1 -0
- package/studio/web/assets/editor-CeRXEhRl.js +1 -0
- package/studio/web/assets/erDiagram-SSCWMZ5O-DJNk6Fgw.js +99 -0
- package/studio/web/assets/eventmodeling-NTZA5JFV-5jbe4A5P.js +1 -0
- package/studio/web/assets/flowDiagram-A5DVABFB-Dp9Ezlow.js +1 -0
- package/studio/web/assets/{ganttDiagram-PKOTCBZU-Cit41Zp3.js → ganttDiagram-EL5Y4UJY-2pOExxMY.js} +2 -2
- package/studio/web/assets/{getAnalytics-rTxZLIM5.js → getAnalytics-D4LKGeVy.js} +1 -1
- package/studio/web/assets/gitGraph-4MIJSDKK-CH5ZxwzF.js +1 -0
- package/studio/web/assets/gitGraphDiagram-WWUBYQGX-DVIsIhbO.js +106 -0
- package/studio/web/assets/html-DOV4s_YT.js +1 -0
- package/studio/web/assets/htmlMode-CdqjnAh4.js +1 -0
- package/studio/web/assets/index-CTqasm6g.js +824 -0
- package/studio/web/assets/index-Cxj2_wsl.css +1 -0
- package/studio/web/assets/index.lazy-VmcKgiEy.js +2 -0
- package/studio/web/assets/info-A6RAGUB7-CPQfTnaG.js +1 -0
- package/studio/web/assets/infoDiagram-RXCK75RN-DlwLYlwm.js +2 -0
- package/studio/web/assets/{ishikawaDiagram-WSZJBQD7-BJYRB-hU.js → ishikawaDiagram-5VMMS53U-BRXRp29U.js} +2 -2
- package/studio/web/assets/{javascript-CGzSV8Sd.js → javascript-Dj7yXfx5.js} +1 -1
- package/studio/web/assets/{journeyDiagram-NVQOT4AX-B8K4wuMz.js → journeyDiagram-EYS64GPL-B0ou8k0n.js} +1 -1
- package/studio/web/assets/jsonMode-khk65qzx.js +1 -0
- package/studio/web/assets/{kanban-definition-27J2QSJJ-DKFV1ovM.js → kanban-definition-3QL26DDD-uYg7iYzp.js} +11 -11
- package/studio/web/assets/languageServices-D_06p7dw.js +1 -0
- package/studio/web/assets/linear-Cnt8506Q.js +1 -0
- package/studio/web/assets/lspLanguageFeatures-DTA6wyQw.js +4 -0
- package/studio/web/assets/mermaid-parser.core-Ck-fC8b7.js +7 -0
- package/studio/web/assets/mermaid.core-CP8aNNYm.js +44 -0
- package/studio/web/assets/{mindmap-definition-FAOFIHXS-CKL4PdAv.js → mindmap-definition-FBJOCRG2-CgTZ-rit.js} +25 -25
- package/studio/web/assets/notifications-Bah46LXX.js +1 -0
- package/studio/web/assets/notifications-CBZXgxVc.js +1 -0
- package/studio/web/assets/packet-AYTQ26CC-DEyoPtPb.js +1 -0
- package/studio/web/assets/pegDiagram-XKGWAZYB-DrD-7sD9.js +1 -0
- package/studio/web/assets/pie-WAS4IAKB-wjj-EI1d.js +1 -0
- package/studio/web/assets/pieDiagram-E7YTZNPT-GntqDCzv.js +39 -0
- package/studio/web/assets/profile-QspOun_j.js +1 -0
- package/studio/web/assets/{quadrantDiagram-CIZ2JOQS-CSbsuWtH.js → quadrantDiagram-AXDQQJYC-0UeqXQGd.js} +1 -1
- package/studio/web/assets/radar-RG4KPBEZ-DFSA5h7k.js +1 -0
- package/studio/web/assets/railroad-74A4TZTK-CaOUG9wR.js +1 -0
- package/studio/web/assets/railroad-abnf-HS5TGJTU-Bc0Qi0WH.js +1 -0
- package/studio/web/assets/railroad-ebnf-LZEXJU2U-G8rVVZ2C.js +1 -0
- package/studio/web/assets/railroad-peg-WCYAUIDC-CrehKBhC.js +1 -0
- package/studio/web/assets/railroadDiagram-O6MQD6OU-yHUELZaV.js +1 -0
- package/studio/web/assets/regions-QdUi7bQ9.js +1 -0
- package/studio/web/assets/{editor.api-eUd8PqEu.js → register-Buvoxmb8.js} +139 -138
- package/studio/web/assets/{requirementDiagram-LRYGKXZP-ZIF0Wlho.js → requirementDiagram-EFPCY7ZU-DNEGFjuW.js} +2 -2
- package/studio/web/assets/rolldown-runtime-hePW80VL.js +1 -0
- package/studio/web/assets/safeParse-DeaoCCWH.js +1 -0
- package/studio/web/assets/{sankeyDiagram-W5VNT64P-BcRESt9x.js → sankeyDiagram-P5KCCOFB-DpyAmSVR.js} +1 -1
- package/studio/web/assets/{sequenceDiagram-SI44F4Z6-OpgTeJNU.js → sequenceDiagram-WJ2MYXX4-TyaT7xNk.js} +1 -1
- package/studio/web/assets/setComponentFile-BI4E_iDe.js +1 -0
- package/studio/web/assets/setLocalStorage-D_kflv4U.js +1 -0
- package/studio/web/assets/setSessionStorage-B0bf71m4.js +1 -0
- package/studio/web/assets/setup-Cop6LBQN.js +2 -0
- package/studio/web/assets/src-CMc7m_Ut.js +1 -0
- package/studio/web/assets/stateDiagram-HBIQ2CUA-CeEdTArZ.js +1 -0
- package/studio/web/assets/stateDiagram-v2-4QOOHH4V-D4tuw9Su.js +1 -0
- package/studio/web/assets/status-DRu3zRgl.js +61 -0
- package/studio/web/assets/step-vX8bovaY.js +1 -0
- package/studio/web/assets/swimlanes-XN3QIQJK-B54FmF46.js +1 -0
- package/studio/web/assets/swimlanesDiagram-VK2B7HYN-XOhmNEvq.js +8 -0
- package/studio/web/assets/tabs-B_G5zscN.js +1 -0
- package/studio/web/assets/{time-D55sDum-.js → time-Cclkb7kQ.js} +1 -1
- package/studio/web/assets/{timeline-definition-Z64GVDOM-Dy5A_tK2.js → timeline-definition-24CTP7MA-D-a9ujbo.js} +5 -5
- package/studio/web/assets/toggleHighContrast-C0dJLmZ0.js +49 -0
- package/studio/web/assets/treeView-Q6P3EWNA-CrW_6JnS.js +1 -0
- package/studio/web/assets/treemap-WGGIJYW6-BxyYLdP_.js +1 -0
- package/studio/web/assets/tsMode-dbDiapTA.js +11 -0
- package/studio/web/assets/typescript-CT1826cL.js +1 -0
- package/studio/web/assets/useEntityRestURL-D1u1X_gE.js +1 -0
- package/studio/web/assets/useLocalStorage-Dtj1QS8_.js +1 -0
- package/studio/web/assets/{vendor-core-CddkYGh5.js → vendor-core-RCcadM3e.js} +1 -1
- package/studio/web/assets/vendor-datadog-BRv-mOv1.js +6 -0
- package/studio/web/assets/{vendor-react-BhazFIp8.js → vendor-react-Dyj4O3HE.js} +1 -1
- package/studio/web/assets/vendor-tanstack-BiFWSB3W.js +1 -0
- package/studio/web/assets/{vendor-ui-BLIveqaL.js → vendor-ui-vhu-UHhF.js} +11 -11
- package/studio/web/assets/{vennDiagram-T6HMQDX7-DRvMIiSo.js → vennDiagram-4TSXK5OY-Cy7s7Mpy.js} +3 -3
- package/studio/web/assets/wardley-WFR3VGLG-BeBL35g2.js +1 -0
- package/studio/web/assets/{wardleyDiagram-T6FBY63Y-B04FSA0i.js → wardleyDiagram-VM6X3IG4-BylmIGSg.js} +3 -3
- package/studio/web/assets/workers-FpR9_D2K.js +3 -0
- package/studio/web/assets/x-B9o9hsep.js +1 -0
- package/studio/web/assets/xml-ZjKUMjth.js +1 -0
- package/studio/web/assets/{xychartDiagram-ELKLHX3M-BE0X7RA7.js → xychartDiagram-S5SC5T6Z-CoKALMXr.js} +3 -3
- package/studio/web/assets/yaml-B6gXbZU6.js +1 -0
- package/studio/web/assets/{yaml.worker-Iyy2Pf1T.js → yaml.worker-BDL8_iuk.js} +153 -153
- package/studio/web/harper-studio_black.png +0 -0
- package/studio/web/index.html +18 -24
- package/utility/processManagement/processManagement.js +19 -3
- package/studio/web/assets/FloatingChat-jLWvR4kI.js +0 -23
- package/studio/web/assets/abnfDiagram-N423BO3Z-BRuH0ywn.js +0 -1
- package/studio/web/assets/apiToken-DYI4DqWa.js +0 -1
- package/studio/web/assets/applications-DCmc9-lk.js +0 -296
- package/studio/web/assets/arc-3Xw6uabx.js +0 -1
- package/studio/web/assets/architecture-TIHT7OUA-CsHfPErM.js +0 -1
- package/studio/web/assets/architectureDiagram-T3A2C74G-DrwbS7Gp.js +0 -36
- package/studio/web/assets/array-BifhSqXX.js +0 -1
- package/studio/web/assets/authStore-B3IhXrn6.js +0 -3
- package/studio/web/assets/blockDiagram-VBNYF7ZC-DKN1mht5.js +0 -132
- package/studio/web/assets/c4Diagram-5PPSVZJV-CT9bmjm_.js +0 -10
- package/studio/web/assets/card-ByrPS_wa.js +0 -1
- package/studio/web/assets/channel-mnKBfsP-.js +0 -1
- package/studio/web/assets/chevron-right-CY-tr2sG.js +0 -1
- package/studio/web/assets/chunk-3NCLNEKW-BVj72ixc.js +0 -1
- package/studio/web/assets/chunk-5FCAYU7R-Dh5nylU8.js +0 -1
- package/studio/web/assets/chunk-5HE753X5-Bw-yg4kO.js +0 -1
- package/studio/web/assets/chunk-5JV3BV7I-Cy5pj3QY.js +0 -1
- package/studio/web/assets/chunk-5TONJI2A-DQOY3q1H.js +0 -2
- package/studio/web/assets/chunk-7BUUIJ7U-Bb538aSH.js +0 -1
- package/studio/web/assets/chunk-7Z6QIM7H-BJhESwQK.js +0 -10
- package/studio/web/assets/chunk-BIQX33UG-DZEsGuSn.js +0 -1
- package/studio/web/assets/chunk-CQNSW5MT-B8S09OIh.js +0 -1
- package/studio/web/assets/chunk-CYSBUYHQ-C1Rf4art.js +0 -1
- package/studio/web/assets/chunk-EMLP6XTP-RLD4nXpI.js +0 -1
- package/studio/web/assets/chunk-I66GZJ75-DHPYnKNr.js +0 -125
- package/studio/web/assets/chunk-J7OUQ5F2-DcY_kb6i.js +0 -2
- package/studio/web/assets/chunk-JG7HCLWE-BYfiSaVt.js +0 -2
- package/studio/web/assets/chunk-JQJVKLGR-DtEmbHZj.js +0 -156
- package/studio/web/assets/chunk-MOZMSUNE-XVucA-n6.js +0 -1
- package/studio/web/assets/chunk-NSK5VX7P-DDFNzCU_.js +0 -2
- package/studio/web/assets/chunk-OSBZ3O6U-CrKZOUOr.js +0 -1
- package/studio/web/assets/chunk-QBLGF6JB-CxAyS2mW.js +0 -1
- package/studio/web/assets/chunk-QR6OTTB3-Df7OCENk.js +0 -62
- package/studio/web/assets/chunk-R7FJI6CG-Cnn3gE-Y.js +0 -1
- package/studio/web/assets/chunk-RYQCIY6F-N0MDKk5G.js +0 -1
- package/studio/web/assets/chunk-U6XO7XAA-BFxjP5U3.js +0 -2
- package/studio/web/assets/chunk-UBXNYLIW-rn0yw1mp.js +0 -1
- package/studio/web/assets/chunk-W5SLKNZC-DKMlAWZk.js +0 -1
- package/studio/web/assets/chunk-WRU74C26-Dg10j8Pf.js +0 -70
- package/studio/web/assets/chunk-YOTPTUD7-D0vRKqYp.js +0 -1
- package/studio/web/assets/chunk-ZIRB5QZD-O39ZBUk3.js +0 -32
- package/studio/web/assets/classDiagram-JCYQIIEL-BrXNP4fl.js +0 -1
- package/studio/web/assets/classDiagram-v2-OCEON4UE-BrXNP4fl.js +0 -1
- package/studio/web/assets/cssMode-DipmMR_-.js +0 -1
- package/studio/web/assets/cynefin-VYW2F7L2-Ds2Fw2Y0.js +0 -1
- package/studio/web/assets/cynefinDiagram-MW4NZA55-DcTzrsC5.js +0 -62
- package/studio/web/assets/cytoscape.esm-B-NFISlW.js +0 -321
- package/studio/web/assets/dagre-Buvkdvvj.js +0 -1
- package/studio/web/assets/dagre-VZM6K2ZE-CMyJ9QPU.js +0 -4
- package/studio/web/assets/diagram-7IWD3JNH-jEGuzdp3.js +0 -30
- package/studio/web/assets/diagram-B4RE2ZJO-Ba7wC7iw.js +0 -3
- package/studio/web/assets/diagram-LBJQPF4R-Bm7Wj_dF.js +0 -24
- package/studio/web/assets/diagram-Q27KOJAE-C-gdktLC.js +0 -24
- package/studio/web/assets/diagram-UB23O5K3-COBVIEEk.js +0 -41
- package/studio/web/assets/ebnfDiagram-BXEA7PRR-G-t56mVV.js +0 -1
- package/studio/web/assets/editor-BDfNodZS.js +0 -1
- package/studio/web/assets/erDiagram-JOGREHBK-DpHzpqx1.js +0 -85
- package/studio/web/assets/eventmodeling-45OFAUF4-CLEiVx5s.js +0 -1
- package/studio/web/assets/flowDiagram-UKHOOZJN-CIxxUUxn.js +0 -1
- package/studio/web/assets/getRegistrationInfo--NLLrics.js +0 -1
- package/studio/web/assets/gitGraph-TEB2WS4Q-CZFBqwW-.js +0 -1
- package/studio/web/assets/gitGraphDiagram-DS77QQ5N-DuGnRv2R.js +0 -106
- package/studio/web/assets/html-DmSSmgE8.js +0 -1
- package/studio/web/assets/htmlMode-B2LqJw1z.js +0 -1
- package/studio/web/assets/index-BO3NaEw8.css +0 -1
- package/studio/web/assets/index-BnYZFLIB.css +0 -1
- package/studio/web/assets/index-CqbCnqsT.js +0 -810
- package/studio/web/assets/index.lazy-BNqr3j0f.js +0 -13
- package/studio/web/assets/info-DKCQHKI2--1InqQeT.js +0 -1
- package/studio/web/assets/infoDiagram-6WML65LV-DliK38kp.js +0 -2
- package/studio/web/assets/isArrayLikeObject-BHDnhloL.js +0 -1
- package/studio/web/assets/jsonMode-BVS6P5Xk.js +0 -1
- package/studio/web/assets/languageServices-Bl-BvoAb.js +0 -1
- package/studio/web/assets/line-Bom3IjHi.js +0 -1
- package/studio/web/assets/linear-DwB_ruUT.js +0 -1
- package/studio/web/assets/lspLanguageFeatures-C7XtZA_w.js +0 -4
- package/studio/web/assets/map-BaFkSB1l.js +0 -1
- package/studio/web/assets/math-Bi_r_ZWc.js +0 -1
- package/studio/web/assets/mermaid-parser.core-Dd4RYr9Q.js +0 -4
- package/studio/web/assets/mermaid.core-Bbo24DGo.js +0 -11
- package/studio/web/assets/notifications-Bb2cj14_.js +0 -1
- package/studio/web/assets/notifications-DVlKonbI.js +0 -1
- package/studio/web/assets/packet-7NZHBO7P-BGMXB0XL.js +0 -1
- package/studio/web/assets/path-fybaL0A-.js +0 -1
- package/studio/web/assets/pegDiagram-VL7TDLO6-CWEktcBB.js +0 -1
- package/studio/web/assets/pie-RZYD4A2V-DgYg3tCt.js +0 -1
- package/studio/web/assets/pieDiagram-7S7Q4E2Y-DEAmZOqY.js +0 -39
- package/studio/web/assets/pollUnlessForbidden-DIDXGBc5.js +0 -1
- package/studio/web/assets/profile-BSQWfYfq.js +0 -1
- package/studio/web/assets/purify.es-DrVcFY1Q.js +0 -3
- package/studio/web/assets/queryClient-BLhQvmWC.js +0 -1
- package/studio/web/assets/radar-I7S5WNFK-Ag-zp_Rq.js +0 -1
- package/studio/web/assets/railroad-3IZDKUUU-aibJDIDV.js +0 -1
- package/studio/web/assets/railroad-abnf-AHOZXSZD-BBMEuvIl.js +0 -1
- package/studio/web/assets/railroad-ebnf-EBAXGLYW-Be1nseej.js +0 -1
- package/studio/web/assets/railroad-peg-LSFZ7HO6-DIvD-Eyb.js +0 -1
- package/studio/web/assets/railroadDiagram-AXF67PYL-BJWrgR10.js +0 -1
- package/studio/web/assets/react-VeAY02s6.js +0 -1
- package/studio/web/assets/react-redux-Ds7wf7Ax.js +0 -5
- package/studio/web/assets/regions-Dk3g8bBF.js +0 -1
- package/studio/web/assets/register-3F1Tje_F.js +0 -2
- package/studio/web/assets/register-S_PdXsoi.js +0 -2
- package/studio/web/assets/rolldown-runtime-B0Z9INg1.js +0 -1
- package/studio/web/assets/select-CKgJgpkn.js +0 -1
- package/studio/web/assets/setComponentFile-DoITgNLb.js +0 -1
- package/studio/web/assets/setLocalStorage-CD_L8p_D.js +0 -1
- package/studio/web/assets/setup-CiEALLa7.js +0 -2
- package/studio/web/assets/src-DUvBCJIr.js +0 -1
- package/studio/web/assets/stateDiagram-OKZ733FA-D9oPLTjO.js +0 -1
- package/studio/web/assets/stateDiagram-v2-UEYNNEHI-CSD1wbuZ.js +0 -1
- package/studio/web/assets/status-CK9tD-Nd.js +0 -61
- package/studio/web/assets/string-f6JzSHaO.js +0 -1
- package/studio/web/assets/swagger-ui-react-CxjCDFB-.js +0 -240
- package/studio/web/assets/swimlanes-SLNWSIFB-BpeoNE9k.js +0 -2
- package/studio/web/assets/swimlanesDiagram-ULZ7WXOC-DHxwzxpp.js +0 -8
- package/studio/web/assets/table-BvFflPWi.js +0 -1
- package/studio/web/assets/tabs-Dd_ElZ36.js +0 -1
- package/studio/web/assets/textarea-a3RnTY31.js +0 -9
- package/studio/web/assets/toggleHighContrast-CyNC3SCQ.js +0 -49
- package/studio/web/assets/tokenization-CWuvR2gh.js +0 -7
- package/studio/web/assets/treeView-QDETBFTQ-uHZ-3Fz8.js +0 -1
- package/studio/web/assets/treemap-6X3UGDF4-BnUKyy3l.js +0 -1
- package/studio/web/assets/tsMode-CslzhcVk.js +0 -11
- package/studio/web/assets/typescript-Dtnury3t.js +0 -1
- package/studio/web/assets/useAuth-DiJUZS1C.js +0 -1
- package/studio/web/assets/useEntityRestURL-Cy-0i1UZ.js +0 -1
- package/studio/web/assets/useLocalStorage-DetQlhBe.js +0 -1
- package/studio/web/assets/vendor-datadog-XDrpA25D.js +0 -6
- package/studio/web/assets/vendor-tanstack-FsM5XZNE.js +0 -1
- package/studio/web/assets/wardley-OPB4EBWU-Da7TLC2F.js +0 -1
- package/studio/web/assets/workers-BtibQGyk.js +0 -3
- package/studio/web/assets/x-D39AGKMb.js +0 -1
- package/studio/web/assets/xml-D41zWqPl.js +0 -1
- package/studio/web/assets/yaml-BFQ3mF1U.js +0 -1
- /package/studio/web/assets/{editor-jjEx9u7D.css → register-jjEx9u7D.css} +0 -0
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@harperfast/harper",
|
|
3
3
|
"description": "Harper is an open-source Node.js performance platform that unifies database, cache, application, and messaging layers into one in-memory process.",
|
|
4
|
-
"version": "5.2.
|
|
4
|
+
"version": "5.2.7",
|
|
5
5
|
"license": "Apache-2.0",
|
|
6
6
|
"homepage": "https://harper.fast",
|
|
7
7
|
"bugs": {
|
|
@@ -244,7 +244,7 @@
|
|
|
244
244
|
"ses": "^1.15.0",
|
|
245
245
|
"stream-chain": "2.2.5",
|
|
246
246
|
"stream-json": "1.9.1",
|
|
247
|
-
"structon": "1.0
|
|
247
|
+
"structon": "1.1.0",
|
|
248
248
|
"systeminformation": "^5.31.4",
|
|
249
249
|
"tar-fs": "^3.1.2",
|
|
250
250
|
"tar-stream": "^3.1.8",
|
package/resources/DESIGN.md
CHANGED
|
@@ -78,25 +78,25 @@ One giant `makeTable()` factory that returns a `TableResource extends Resource`
|
|
|
78
78
|
|
|
79
79
|
## "Where is X" cheat sheet
|
|
80
80
|
|
|
81
|
-
| Question | Where
|
|
82
|
-
| --------------------------------------------------------------------- |
|
|
83
|
-
| How is a CRUD request authorized? | `Table.ts → #section: authz-hooks`; defaults in `Resource.ts` (`allowRead` etc.)
|
|
84
|
-
| Where does versioning / conflict resolution happen? | `Table.ts → _writeUpdate` (`#section: write-path-internals`)
|
|
85
|
-
| How does `search()` choose an index? | `Table.ts → search` (`#section: search-query`)
|
|
86
|
-
| How are subscriptions replayed? | `Table.ts → subscribe` (`#section: pub-sub`)
|
|
87
|
-
| How is the response body shaped (select clause)? | `Table.ts → transformEntryForSelect` (`#section: search-query`)
|
|
88
|
-
| Where is record-level TTL evaluated? | `Table.ts → setTTLExpiration` (`#section: lifecycle-admin`); `Updatable.getExpiresAt` (`#section: setup-and-factory`). Stored expiry metadata is resolved in the `_writeUpdate` commit closure: `options.expiresAt ?? context.expiresAt ?? (record @expiresAt field, if finite & ≥ 0) ?? table default`. This metadata drives read-hiding + the cleanup sweep. The `@expiresAt` attribute is authoritative for **direct** put/patch only; cache/source fills persist via `recordUpdater` and derive expiry from `sourceContext.expiresAt` (source freshness / table default), not the field.
|
|
89
|
-
| Why does `search()` hide a row that's past its TTL but not yet swept? | `Table.ts → transformEntryForSelect` unconditionally treats `entry.expiresAt < Date.now()` as gone (lazy eviction on read) — correct for a SELECT, but a mutation locating rows to overwrite needs the opposite: pass `target.includeExpired = true` (read by the SQL engine's `runUpdate`/`runDelete` via `SqlEngineContext.includeExpiredRows`) to treat such a row as a live match, matching the leniency a direct by-id `put`/`patch` already has (they skip this check entirely, since `Resource.patch`'s static options don't request `ensureLoaded`).
|
|
90
|
-
| How are residencies enforced (replication)? | `Table.ts → #section: lifecycle-admin` (residency block: `getResidencyRecord`, `setResidency`, `setResidencyById`, `getResidency`)
|
|
91
|
-
| How is the RecordObject prototype applied? | `RecordEncoder.ts` (see `../DESIGN.md`)
|
|
92
|
-
| Where is the per-request transaction stored? | `transaction.ts` + `contextStorage` (AsyncLocalStorage)
|
|
93
|
-
| What is in `context.transaction` when no scope owns one? | Whatever the last table call left there. A completed scope leaves `RELEASED_TRANSACTION` (`DatabaseTransaction.ts`), which reads latest and no-ops on commit. The next table call finds no transaction and `Table.ts → txnForContext` installs an `ImmediateTransaction` in the slot.
|
|
94
|
-
| How does a query opt out of a read snapshot? | Pass `snapshot: false` on the search request (e.g. `get_analytics`). `Table.ts → search` calls `txn.useReadTxn(snapshot === false)`; on RocksDB `DatabaseTransaction.getReadTxn` then builds the read txn with `{ disableSnapshot: true }` so a long scan reads latest without pinning a snapshot. No-op on LMDB (`LMDBTransaction.useReadTxn`).
|
|
95
|
-
| How does a URL path map to a Resource? | `Resources.ts → getMatch` (exact/prefix fast path) then `matchParamRoute` (parameterised routes); see "Path routing" below
|
|
96
|
-
| How does HNSW keep the graph connected on delete? | `indexes/HierarchicalNavigableSmallWorld.ts → index()` delete path: zero-degree orphans reindexed via `needsReindexing`; severed multi-node islands detected and reconnected by `repairSeveredNeighbors` (#1712)
|
|
97
|
-
| How is a filter applied _during_ a vector search? | Predicate-aware traversal (#1241): `search.ts → executeConditions` composes companion AND conditions with request `vectorFilter` / `rowFilter` predicates into one `(primaryKey) => boolean` (`composeRecordFilter`) and passes it to `HierarchicalNavigableSmallWorld.search(cond, ctx, filter)`. The filter gates result admission at layer 0 only (routing ignores it, ACORN-style); a visit budget (`filterExpansion`) bounds the under-filled/selective case. Very selective _condition_ filters are instead diverted to the exact brute-force path by the query planner's `estimateCountAsSort` ordering.
|
|
98
|
-
| How does post-ordering resolve vector distances safely? | Each comparator owns its `Sort`, passes it directly to the custom-index resolver, and caches distances by that immutable per-query sort object.
|
|
99
|
-
| How is application row filtering applied? | Authorization admission happens in the resource operation before query work. The legacy `allow*` hook, when armed by the protocol, is evaluated once with its historical receiver semantics; overriding it never changes its scope. An operation override may add indexed conditions and/or attach the JavaScript-only synchronous `target.rowFilter(record, context)`. `Table.search` composes it with query filters and rechecks the final materialized cache/source record. `SubscriptionRequest.rowFilter` covers full-row events; `eventFilter(event, context)` explicitly handles tombstones/messages/raw events. Prefer indexed conditions because an opaque predicate may inspect every admitted candidate and `limit` applies after filtering.
|
|
81
|
+
| Question | Where |
|
|
82
|
+
| --------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
83
|
+
| How is a CRUD request authorized? | `Table.ts → #section: authz-hooks`; defaults in `Resource.ts` (`allowRead` etc.) |
|
|
84
|
+
| Where does versioning / conflict resolution happen? | `Table.ts → _writeUpdate` (`#section: write-path-internals`) |
|
|
85
|
+
| How does `search()` choose an index? | `Table.ts → search` (`#section: search-query`) |
|
|
86
|
+
| How are subscriptions replayed? | `Table.ts → subscribe` (`#section: pub-sub`) |
|
|
87
|
+
| How is the response body shaped (select clause)? | `Table.ts → transformEntryForSelect` (`#section: search-query`) |
|
|
88
|
+
| Where is record-level TTL evaluated? | `Table.ts → setTTLExpiration` (`#section: lifecycle-admin`); `Updatable.getExpiresAt` (`#section: setup-and-factory`). Stored expiry metadata is resolved in the `_writeUpdate` commit closure: `options.expiresAt ?? context.expiresAt ?? (record @expiresAt field, if finite & ≥ 0) ?? table default`. This metadata drives read-hiding + the cleanup sweep. The `@expiresAt` attribute is authoritative for **direct** put/patch only; cache/source fills persist via `recordUpdater` and derive expiry from `sourceContext.expiresAt` (source freshness / table default), not the field. |
|
|
89
|
+
| Why does `search()` hide a row that's past its TTL but not yet swept? | `Table.ts → transformEntryForSelect` unconditionally treats `entry.expiresAt < Date.now()` as gone (lazy eviction on read) — correct for a SELECT, but a mutation locating rows to overwrite needs the opposite: pass `target.includeExpired = true` (read by the SQL engine's `runUpdate`/`runDelete` via `SqlEngineContext.includeExpiredRows`) to treat such a row as a live match, matching the leniency a direct by-id `put`/`patch` already has (they skip this check entirely, since `Resource.patch`'s static options don't request `ensureLoaded`). |
|
|
90
|
+
| How are residencies enforced (replication)? | `Table.ts → #section: lifecycle-admin` (residency block: `getResidencyRecord`, `setResidency`, `setResidencyById`, `getResidency`) |
|
|
91
|
+
| How is the RecordObject prototype applied? | `RecordEncoder.ts` (see `../DESIGN.md`) |
|
|
92
|
+
| Where is the per-request transaction stored? | `transaction.ts` + `contextStorage` (AsyncLocalStorage) |
|
|
93
|
+
| What is in `context.transaction` when no scope owns one? | Whatever the last table call left there. A completed scope leaves `RELEASED_TRANSACTION` (`DatabaseTransaction.ts`), which reads latest and no-ops on commit. The next table call finds no transaction and `Table.ts → txnForContext` installs an `ImmediateTransaction` in the slot. It reports `open === OPEN`, but `transaction()` and `Resource`'s dispatcher gate on `isJoinableScope` — OPEN _and_ staging its writes for a later commit — so they never join it and start their own scope instead (#2292); `txnForContext` keeps returning it for reads and for writes that reach no wrapper, and those commit per write. It is also the only transaction that opens its native handle inside its own `commit()` (its `getReadTxn` never opens one), which is why `commit()` re-reads `this.transaction` after its save loop (#2288). |
|
|
94
|
+
| How does a query opt out of a read snapshot? | Pass `snapshot: false` on the search request (e.g. `get_analytics`). `Table.ts → search` calls `txn.useReadTxn(snapshot === false)`; on RocksDB `DatabaseTransaction.getReadTxn` then builds the read txn with `{ disableSnapshot: true }` so a long scan reads latest without pinning a snapshot. No-op on LMDB (`LMDBTransaction.useReadTxn`). |
|
|
95
|
+
| How does a URL path map to a Resource? | `Resources.ts → getMatch` (exact/prefix fast path) then `matchParamRoute` (parameterised routes); see "Path routing" below |
|
|
96
|
+
| How does HNSW keep the graph connected on delete? | `indexes/HierarchicalNavigableSmallWorld.ts → index()` delete path: zero-degree orphans reindexed via `needsReindexing`; severed multi-node islands detected and reconnected by `repairSeveredNeighbors` (#1712) |
|
|
97
|
+
| How is a filter applied _during_ a vector search? | Predicate-aware traversal (#1241): `search.ts → executeConditions` composes companion AND conditions with request `vectorFilter` / `rowFilter` predicates into one `(primaryKey) => boolean` (`composeRecordFilter`) and passes it to `HierarchicalNavigableSmallWorld.search(cond, ctx, filter)`. The filter gates result admission at layer 0 only (routing ignores it, ACORN-style); a visit budget (`filterExpansion`) bounds the under-filled/selective case. Very selective _condition_ filters are instead diverted to the exact brute-force path by the query planner's `estimateCountAsSort` ordering. |
|
|
98
|
+
| How does post-ordering resolve vector distances safely? | Each comparator owns its `Sort`, passes it directly to the custom-index resolver, and caches distances by that immutable per-query sort object. |
|
|
99
|
+
| How is application row filtering applied? | Authorization admission happens in the resource operation before query work. The legacy `allow*` hook, when armed by the protocol, is evaluated once with its historical receiver semantics; overriding it never changes its scope. An operation override may add indexed conditions and/or attach the JavaScript-only synchronous `target.rowFilter(record, context)`. `Table.search` composes it with query filters and rechecks the final materialized cache/source record. `SubscriptionRequest.rowFilter` covers full-row events; `eventFilter(event, context)` explicitly handles tombstones/messages/raw events. Prefer indexed conditions because an opaque predicate may inspect every admitted candidate and `limit` applies after filtering. |
|
|
100
100
|
|
|
101
101
|
**QUERY admission uses the body projection.** `Resource.transactional` resolves an asynchronous HTTP QUERY body before resource resolution, recursively clones away client-supplied `checkPermission`, and copies the body's `select` onto the operation admission target before `allowRead`. After authorization, `Resource.query` transfers only the narrowed projection to the body target. This ensures a body-only relationship select is checked before `Table.search`; permission-control fields from QUERY data must never reach a search target.
|
|
102
102
|
|
|
@@ -166,6 +166,11 @@ Tests: `../unitTests/resources/defineResource.test.js`, `../unitTests/resources/
|
|
|
166
166
|
|
|
167
167
|
## Conventions
|
|
168
168
|
|
|
169
|
+
- A record's `toJSON` is a response projection, not durable state. `recordUpdater` (and the cache-fill
|
|
170
|
+
and audit paths feeding it) projects records to stored fields via `storedFieldsOnly`, so no durable
|
|
171
|
+
form carries a resolver-owned (`@computed`/`@relationship`) name; materialization skips such names
|
|
172
|
+
when promoting a decoded record (`assignStoredFields`), which is what keeps rows written by affected
|
|
173
|
+
releases readable. See harper#2359 and `integrationTests/resources/cachedComputedAttribute.test.ts`.
|
|
169
174
|
- **Never** remove `transactional()` from a static method on `Resource` — it owns transaction context lifetime.
|
|
170
175
|
- New `Resource` subclasses should override **instance** methods (`get`, `put`, ...) for behavior; static methods are the protocol entry points and stay generic.
|
|
171
176
|
- **Overriding a static entry point takes over its whole contract.** Assigning `static post`/`put`/`patch` on a subclass shadows the `transactional()`-wrapped static, so none of that wrapper runs — including the `when(data, ...)` that resolves `data` and the `allowCreate`/`allowUpdate` gate. That is by design (it is how `login.ts` implements a deliberately pre-authentication endpoint), and it means an override owns two obligations the wrapper would otherwise have met:
|
|
@@ -261,7 +261,9 @@ export type TransactionWrite = {
|
|
|
261
261
|
before?: () => void | Promise<void>;
|
|
262
262
|
beforeIntermediate?: () => void | Promise<void>;
|
|
263
263
|
commit?: (txnTime: number, existingEntry: Partial<Entry>, retry: boolean, transaction: any) => MaybePromise<void>;
|
|
264
|
-
|
|
264
|
+
// Once a write has been taken over, the transaction committing it is not the one that staged it, and
|
|
265
|
+
// overload accounting, the replay marker and a no-op write's removal all belong to the committer.
|
|
266
|
+
validate?: (txnTime: number, committedBy: DatabaseTransaction) => void;
|
|
265
267
|
fullUpdate?: boolean;
|
|
266
268
|
saved?: boolean;
|
|
267
269
|
deferSave?: boolean;
|
|
@@ -278,6 +280,10 @@ export type TransactionWrite = {
|
|
|
278
280
|
// sticky: a non-isRetry staging of this write appended its audit entry (set in save(); the retry
|
|
279
281
|
// dedup guards in the commit handler read it to ignore the write's own orphaned entry)
|
|
280
282
|
appendedAuditEntry?: boolean;
|
|
283
|
+
// the transaction holding this write in its `writes` (set in addWrite). A deferred write's save() is
|
|
284
|
+
// only its trigger, so it can be triggered after the context has moved on to another transaction;
|
|
285
|
+
// this is who commits it when the transaction current at that point is not a scope (#2292).
|
|
286
|
+
stagedIn?: DatabaseTransaction;
|
|
281
287
|
// the preceding write to the same store and key in this transaction, if any (linked in addWrite)
|
|
282
288
|
priorWrite?: TransactionWrite;
|
|
283
289
|
// set only by a write that BOTH reads priorStagedWrite() and publishes stagedEntry; addWrite orders
|
|
@@ -361,6 +367,11 @@ export class DatabaseTransaction implements Transaction {
|
|
|
361
367
|
timeoutBudget = 0;
|
|
362
368
|
// save() only stages here; ImmediateTransaction overrides it to commit, which addWrite must not defer
|
|
363
369
|
saveCommits = false;
|
|
370
|
+
// True where save() puts the write into this transaction's native handle, which is what lets a scope
|
|
371
|
+
// take over a write staged in another transaction's `writes` (Table.ts's #saveOperation).
|
|
372
|
+
// LMDBTransaction's save() is a no-op — its commit applies `writes` — so there a write can only be
|
|
373
|
+
// committed by the transaction that holds it.
|
|
374
|
+
stagesWriteOnSave = true;
|
|
364
375
|
validated = 0;
|
|
365
376
|
timestamp = 0;
|
|
366
377
|
retries = 0;
|
|
@@ -590,11 +601,47 @@ export class DatabaseTransaction implements Transaction {
|
|
|
590
601
|
writesForStore.set(keyId, operation);
|
|
591
602
|
}
|
|
592
603
|
|
|
604
|
+
/**
|
|
605
|
+
* Drop a staged write from this transaction, and from its per-key chain, so the transaction taking it
|
|
606
|
+
* over becomes its only owner (Table.ts's #saveOperation).
|
|
607
|
+
*/
|
|
608
|
+
detachWrite(operation: TransactionWrite): void {
|
|
609
|
+
const index = this.writes.indexOf(operation);
|
|
610
|
+
if (index > -1) this.writes[index] = null;
|
|
611
|
+
if (operation.key === undefined) return;
|
|
612
|
+
const writesForStore = this.writesByKey?.get(operation.store);
|
|
613
|
+
if (!writesForStore) return;
|
|
614
|
+
const keyId = writeKeyId(operation.key);
|
|
615
|
+
// Membership, not `stagedIn`, which every commit handler clears and so cannot tell a takeover from a
|
|
616
|
+
// write done in place; a prior already taken over must not become this transaction's basis again.
|
|
617
|
+
let prior = operation.priorWrite;
|
|
618
|
+
while (prior && !this.writes.includes(prior)) prior = prior.priorWrite;
|
|
619
|
+
const tail = writesForStore.get(keyId);
|
|
620
|
+
if (tail === operation) {
|
|
621
|
+
if (prior) writesForStore.set(keyId, prior);
|
|
622
|
+
else writesForStore.delete(keyId);
|
|
623
|
+
return;
|
|
624
|
+
}
|
|
625
|
+
// A successor left chained to it would take its merge basis and index diff from a record another
|
|
626
|
+
// transaction owns and may roll back (harper#1968's failure class).
|
|
627
|
+
for (let successor = tail; successor; successor = successor.priorWrite) {
|
|
628
|
+
if (successor.priorWrite === operation) {
|
|
629
|
+
successor.priorWrite = prior;
|
|
630
|
+
return;
|
|
631
|
+
}
|
|
632
|
+
}
|
|
633
|
+
}
|
|
634
|
+
|
|
593
635
|
/**
|
|
594
636
|
* Discard the staged write set (committed or aborted); the per-key chain must go with it so a
|
|
595
637
|
* reused transaction never bases a write on a previous batch's staged state.
|
|
596
638
|
*/
|
|
597
639
|
clearWrites(): void {
|
|
640
|
+
// A deferred write's `stagedIn` must not outlive this transaction's ability to commit it: save() can
|
|
641
|
+
// fire after a commit or abort, and routing it back here would revive a write this transaction
|
|
642
|
+
// already rolled back — whose blobs abort() has reclaimed. Cleared here, save() resolves the
|
|
643
|
+
// context's current transaction as it did before `stagedIn` existed.
|
|
644
|
+
for (const write of this.writes) if (write?.stagedIn === this) write.stagedIn = undefined;
|
|
598
645
|
this.writes = [];
|
|
599
646
|
this.writesByKey = undefined;
|
|
600
647
|
}
|
|
@@ -729,6 +776,7 @@ export class DatabaseTransaction implements Transaction {
|
|
|
729
776
|
this.writeTimeout = this.timeout;
|
|
730
777
|
this.linkWrite(operation);
|
|
731
778
|
this.writes.push(operation);
|
|
779
|
+
operation.stagedIn = this;
|
|
732
780
|
// Hold this write back while any earlier same-key write has not run — out of staging order both
|
|
733
781
|
// diff against the pre-transaction record (harper#2211, DESIGN.md). The whole chain, not just the
|
|
734
782
|
// immediate link: an eager non-chaining write in between would otherwise launder the deferral.
|
|
@@ -807,7 +855,7 @@ export class DatabaseTransaction implements Transaction {
|
|
|
807
855
|
if (!operation.saved) {
|
|
808
856
|
operation.saved = true;
|
|
809
857
|
// immediately execute in this transaction
|
|
810
|
-
if ((operation.validate?.(txnTime) as any) === false) {
|
|
858
|
+
if ((operation.validate?.(txnTime, this) as any) === false) {
|
|
811
859
|
operation.commit = () => {}; // noop if we try again
|
|
812
860
|
return;
|
|
813
861
|
}
|
|
@@ -1491,6 +1539,19 @@ export function isReleasedTransaction(value: unknown): boolean {
|
|
|
1491
1539
|
return value === RELEASED_TRANSACTION;
|
|
1492
1540
|
}
|
|
1493
1541
|
|
|
1542
|
+
/**
|
|
1543
|
+
* Whether this transaction can be joined as the atomic scope resources/transaction.ts promises. OPEN is
|
|
1544
|
+
* not sufficient: an ImmediateTransaction commits every write as it is made, so a caller that joined one
|
|
1545
|
+
* would get per-write autocommit with no final commit or abort to roll back to. txnForContext installs
|
|
1546
|
+
* one in a context slot that is empty or holds the released placeholder, where it reports OPEN with
|
|
1547
|
+
* nothing owning a commit for it (#2292). Ownership itself is deliberately not the test — a context
|
|
1548
|
+
* pre-seeded with an externally driven DatabaseTransaction (replayLogs.ts) still owns the writes it is
|
|
1549
|
+
* given, and its own commit/abort still governs them.
|
|
1550
|
+
*/
|
|
1551
|
+
export function isJoinableScope(transaction: DatabaseTransaction | null | undefined): boolean {
|
|
1552
|
+
return transaction?.open === TRANSACTION_STATE.OPEN && !transaction.saveCommits;
|
|
1553
|
+
}
|
|
1554
|
+
|
|
1494
1555
|
let timer;
|
|
1495
1556
|
|
|
1496
1557
|
/**
|
|
@@ -25,6 +25,7 @@ export function replicationConfirmation(callback) {
|
|
|
25
25
|
}
|
|
26
26
|
|
|
27
27
|
export class LMDBTransaction extends DatabaseTransaction {
|
|
28
|
+
stagesWriteOnSave = false;
|
|
28
29
|
#context: Context;
|
|
29
30
|
writes: TransactionWrite[] = []; // the set of writes to commit if the conditions are met
|
|
30
31
|
validated = 0;
|
|
@@ -97,6 +98,9 @@ export class LMDBTransaction extends DatabaseTransaction {
|
|
|
97
98
|
const immediateTxn = new ImmediateTransaction(this.db);
|
|
98
99
|
immediateTxn.addWrite(operation);
|
|
99
100
|
const result = immediateTxn.commit({});
|
|
101
|
+
// Nothing may be sent back to this throwaway: the write is already committed, and its
|
|
102
|
+
// durability is the promise below.
|
|
103
|
+
operation.stagedIn = undefined;
|
|
100
104
|
if (result?.then) {
|
|
101
105
|
operation.promise = result;
|
|
102
106
|
} else {
|
|
@@ -107,6 +111,7 @@ export class LMDBTransaction extends DatabaseTransaction {
|
|
|
107
111
|
|
|
108
112
|
this.linkWrite(operation);
|
|
109
113
|
this.writes.push(operation); // standard path, add to current transaction
|
|
114
|
+
operation.stagedIn = this;
|
|
110
115
|
}
|
|
111
116
|
|
|
112
117
|
removeWrite(operation: TransactionWrite) {
|
|
@@ -133,7 +138,7 @@ export class LMDBTransaction extends DatabaseTransaction {
|
|
|
133
138
|
this.validated = this.writes.length;
|
|
134
139
|
for (let i = start; i < this.validated; i++) {
|
|
135
140
|
const write = this.writes[i];
|
|
136
|
-
write?.validate?.(this.timestamp);
|
|
141
|
+
write?.validate?.(this.timestamp, this);
|
|
137
142
|
}
|
|
138
143
|
let hasBefore;
|
|
139
144
|
for (let i = start; i < this.validated; i++) {
|
|
@@ -342,6 +347,7 @@ export class LMDBTransaction extends DatabaseTransaction {
|
|
|
342
347
|
}
|
|
343
348
|
|
|
344
349
|
export class ImmediateTransaction extends LMDBTransaction {
|
|
350
|
+
saveCommits = true;
|
|
345
351
|
constructor(db: RootDatabaseKind) {
|
|
346
352
|
super();
|
|
347
353
|
this.db = db;
|
|
@@ -3,7 +3,7 @@ import { RocksDatabase, type RocksDatabaseOptions, constants, type Store } from
|
|
|
3
3
|
const FRESH_VERSION_FLAG = constants.FRESH_VERSION_FLAG;
|
|
4
4
|
import { WeakLRUCache } from 'weak-lru-cache';
|
|
5
5
|
import { when } from '../utility/when.ts';
|
|
6
|
-
import { entryMap, METADATA, type Entry } from './RecordEncoder.ts';
|
|
6
|
+
import { assignStoredFields, entryMap, METADATA, type Entry } from './RecordEncoder.ts';
|
|
7
7
|
|
|
8
8
|
/**
|
|
9
9
|
* RocksDatabase subclass that owns all primary-store behaviour for Harper tables:
|
|
@@ -60,7 +60,7 @@ export class PrimaryRocksDatabase extends RocksDatabase {
|
|
|
60
60
|
if (entry.value.constructor === Object && this.#enc.structPrototype) {
|
|
61
61
|
const originalValue = entry.value;
|
|
62
62
|
entry.value = new this.#enc.structPrototype.constructor();
|
|
63
|
-
|
|
63
|
+
assignStoredFields(entry.value, originalValue, this.#enc);
|
|
64
64
|
}
|
|
65
65
|
if (typeof entry.value === 'object' && entry.value !== null) {
|
|
66
66
|
entryMap.set(entry.value, entry);
|
|
@@ -164,7 +164,7 @@ export class PrimaryRocksDatabase extends RocksDatabase {
|
|
|
164
164
|
if (entry.value?.constructor === Object && enc.structPrototype) {
|
|
165
165
|
const originalValue = entry.value;
|
|
166
166
|
entry.value = new enc.structPrototype.constructor();
|
|
167
|
-
|
|
167
|
+
assignStoredFields(entry.value, originalValue, enc);
|
|
168
168
|
}
|
|
169
169
|
return entry;
|
|
170
170
|
});
|
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
*/
|
|
7
7
|
|
|
8
8
|
import { Encoder } from 'msgpackr';
|
|
9
|
-
import { createStructon } from 'structon';
|
|
9
|
+
import { createStructon, SOURCE_SYMBOL } from 'structon';
|
|
10
10
|
import {
|
|
11
11
|
HAS_PREVIOUS_RESIDENCY_ID,
|
|
12
12
|
HAS_CURRENT_RESIDENCY_ID,
|
|
@@ -123,12 +123,86 @@ let timestampNextEncoding = 0,
|
|
|
123
123
|
additionalAuditRefsNextEncoding: Array<{ version: number; nodeId: number }> | undefined;
|
|
124
124
|
// tracking metadata with a singleton works better than trying to alter response of getEntry/get and coordinating that across caching layers
|
|
125
125
|
export let lastMetadata: Entry | null = null;
|
|
126
|
+
/**
|
|
127
|
+
* A resolver owns its attribute's name, so no durable form of a record may carry a value under it.
|
|
128
|
+
* A value gets there either through the response `toJSON` the table installs on the record prototype
|
|
129
|
+
* (msgpackr consults an instance's toJSON when it encodes one) or as an own key from a source or
|
|
130
|
+
* peer payload.
|
|
131
|
+
*/
|
|
132
|
+
export function storedFieldsOnly(encoder: any, record: any) {
|
|
133
|
+
if (record === null || typeof record !== 'object') return record;
|
|
134
|
+
// Optional: this also runs for a store whose encode hook was grafted onto a foreign encoder
|
|
135
|
+
// (copyDb's migration target), which carries no table schema.
|
|
136
|
+
const resolved = encoder?.resolvedAttributeNames;
|
|
137
|
+
if (resolved === undefined) return record;
|
|
138
|
+
const resolvedList = encoder.resolvedAttributeNamesList ?? Array.from(resolved);
|
|
139
|
+
// A typed-struct instance keeps its stored fields as accessors on a structon-generated child
|
|
140
|
+
// prototype carrying structon's own toJSON, so it never reaches the response projection — and
|
|
141
|
+
// copying its own keys would encode an empty record.
|
|
142
|
+
let project =
|
|
143
|
+
encoder.surfacedToJSON !== undefined &&
|
|
144
|
+
record.toJSON === encoder.surfacedToJSON &&
|
|
145
|
+
record[SOURCE_SYMBOL] === undefined;
|
|
146
|
+
if (!project && typeof record.toJSON === 'function' && record[SOURCE_SYMBOL] === undefined) {
|
|
147
|
+
// A class instance with its own serialization: the encoder will consult that toJSON, so the
|
|
148
|
+
// projection has to run on its OUTPUT, or the serialized form smuggles resolver-owned keys past
|
|
149
|
+
// a check that only saw the instance. A non-object (or self) result is left for the encoder,
|
|
150
|
+
// which invokes toJSON again — serializers are assumed pure.
|
|
151
|
+
const json = record.toJSON();
|
|
152
|
+
if (json !== record && json !== null && typeof json === 'object') record = json;
|
|
153
|
+
}
|
|
154
|
+
if (!project) {
|
|
155
|
+
for (let i = 0; i < resolvedList.length; i++) {
|
|
156
|
+
if (Object.hasOwn(record, resolvedList[i])) {
|
|
157
|
+
project = true;
|
|
158
|
+
break;
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
if (!project) return record;
|
|
163
|
+
const stored = {};
|
|
164
|
+
for (const key of Object.keys(record)) {
|
|
165
|
+
if (resolved.has(key)) continue;
|
|
166
|
+
// '__proto__' as an own key must stay a data property: plain assignment would run the inherited
|
|
167
|
+
// setter, dropping the field from the durable write and swapping the temporary's prototype.
|
|
168
|
+
if (key === '__proto__')
|
|
169
|
+
Object.defineProperty(stored, key, { value: record[key], enumerable: true, writable: true, configurable: true });
|
|
170
|
+
else stored[key] = record[key];
|
|
171
|
+
}
|
|
172
|
+
return stored;
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
/**
|
|
176
|
+
* A resolver-owned name is skipped rather than assigned during record-instance promotion: through a
|
|
177
|
+
* read-only resolver the assignment throws, and through a writable one (a many-to-one
|
|
178
|
+
* `@relationship`) it would derive a foreign key from the stale stored value.
|
|
179
|
+
*/
|
|
180
|
+
export function assignStoredFields(target: any, source: any, encoder: any) {
|
|
181
|
+
const resolved = encoder?.resolvedAttributeNames;
|
|
182
|
+
if (resolved === undefined) return Object.assign(target, source);
|
|
183
|
+
// Own keys, matching the Object.assign fallback above and storedFieldsOnly: a decoded plain
|
|
184
|
+
// object has no inherited enumerables of its own, so walking the chain could only ever pick up
|
|
185
|
+
// somebody else's (a polluted Object.prototype included).
|
|
186
|
+
for (const key of Object.keys(source)) {
|
|
187
|
+
if (resolved.has(key)) continue;
|
|
188
|
+
if (key === '__proto__')
|
|
189
|
+
Object.defineProperty(target, key, { value: source[key], enumerable: true, writable: true, configurable: true });
|
|
190
|
+
else target[key] = source[key];
|
|
191
|
+
}
|
|
192
|
+
return target;
|
|
193
|
+
}
|
|
194
|
+
|
|
126
195
|
export class RecordEncoder extends StructonEncoder {
|
|
127
196
|
rootStore: any;
|
|
128
197
|
declare saveStructures: any;
|
|
129
198
|
declare getStructures: any;
|
|
130
199
|
declare _writeStruct: any;
|
|
131
200
|
structureUpdate?: any;
|
|
201
|
+
// Set by TableResource.updatedAttributes. Undefined for a store with no resolved attributes, which
|
|
202
|
+
// is what keeps the projection off that store's writes entirely.
|
|
203
|
+
resolvedAttributeNames?: Set<string>;
|
|
204
|
+
resolvedAttributeNamesList?: string[];
|
|
205
|
+
surfacedToJSON?: () => any;
|
|
132
206
|
isRocksDB: boolean;
|
|
133
207
|
name: string;
|
|
134
208
|
useVersions: boolean;
|
|
@@ -539,7 +613,7 @@ export function handleLocalTimeForGets(store, rootStore) {
|
|
|
539
613
|
// if an object was deserialized as a plain object, give it the right prototype for computed properties to be accessible
|
|
540
614
|
const originalValue = entry.value;
|
|
541
615
|
entry.value = new store.encoder.structPrototype.constructor();
|
|
542
|
-
|
|
616
|
+
assignStoredFields(entry.value, originalValue, store.encoder);
|
|
543
617
|
}
|
|
544
618
|
entryMap.set(entry.value, entry); // allow the record to access the entry
|
|
545
619
|
}
|
|
@@ -591,7 +665,7 @@ export function handleLocalTimeForGets(store, rootStore) {
|
|
|
591
665
|
// if an object was deserialized as a plain object, give it the right prototype for computed properties to be accessible
|
|
592
666
|
const originalValue = entry.value;
|
|
593
667
|
entry.value = new store.encoder.structPrototype.constructor();
|
|
594
|
-
|
|
668
|
+
assignStoredFields(entry.value, originalValue, store.encoder);
|
|
595
669
|
}
|
|
596
670
|
}
|
|
597
671
|
return entry;
|
|
@@ -708,6 +782,10 @@ export function recordUpdater(store, tableId, auditStore) {
|
|
|
708
782
|
// harper#1309: reset so a record===undefined call (delete/no-op) cannot carry stale bytes
|
|
709
783
|
// into the audit encodedRecord or write-size analytics of this call.
|
|
710
784
|
lastValueEncoding = undefined;
|
|
785
|
+
// Every durable form of the record — the stored value, the audit entry's encodedRecord and so the
|
|
786
|
+
// transaction log and replication payload — comes from this record, so projecting it here covers
|
|
787
|
+
// them all; nested values keep their own toJSON serialization.
|
|
788
|
+
record = storedFieldsOnly(store.encoder, record);
|
|
711
789
|
const isRocksDB = store instanceof RocksDatabase;
|
|
712
790
|
// determine if and how we apply the local timestamp
|
|
713
791
|
if (isRocksDB) {
|
|
@@ -809,6 +887,10 @@ export function recordUpdater(store, tableId, auditStore) {
|
|
|
809
887
|
if (audit) {
|
|
810
888
|
const username = typeof options?.user === 'string' ? options.user : options?.user?.username;
|
|
811
889
|
if (auditRecord) {
|
|
890
|
+
// The audit encodedRecord is durable record state too (it is the transaction log and the
|
|
891
|
+
// replication payload), so it takes the same projection — except for message/publish
|
|
892
|
+
// entries, whose auditRecord IS the published payload and must reach subscribers verbatim.
|
|
893
|
+
if (type !== 'message' && type !== 'publish') auditRecord = storedFieldsOnly(store.encoder, auditRecord);
|
|
812
894
|
encodeBlobsWithFilePath(() => store.encoder.encode(auditRecord), id, store.rootStore);
|
|
813
895
|
if (blobsWereEncoded) {
|
|
814
896
|
extendedType |= HAS_BLOBS;
|
package/resources/Resource.ts
CHANGED
|
@@ -12,8 +12,8 @@ import {
|
|
|
12
12
|
import { randomUUID } from 'crypto';
|
|
13
13
|
import {
|
|
14
14
|
DatabaseTransaction,
|
|
15
|
+
isJoinableScope,
|
|
15
16
|
isReleasedTransaction,
|
|
16
|
-
TRANSACTION_STATE,
|
|
17
17
|
type Transaction,
|
|
18
18
|
} from './DatabaseTransaction.ts';
|
|
19
19
|
import { IterableEventQueue } from './IterableEventQueue.ts';
|
|
@@ -769,9 +769,10 @@ function transactional(
|
|
|
769
769
|
if (isCollection) resourceOptions.isCollection = true;
|
|
770
770
|
} else resourceOptions = options;
|
|
771
771
|
const loadAsInstance = this.loadAsInstance;
|
|
772
|
-
// Only join an existing transaction
|
|
773
|
-
//
|
|
774
|
-
//
|
|
772
|
+
// Only join an existing transaction that can actually be a scope (isJoinableScope: OPEN, and it
|
|
773
|
+
// stages its writes rather than committing each one — the same gate resources/transaction.ts's
|
|
774
|
+
// transaction() helper applies to itself). Beyond that, a `context` object can carry a *stale*
|
|
775
|
+
// `.transaction` left over from an earlier, unrelated call that
|
|
775
776
|
// already ran to completion: ambient contexts obtained via contextStorage.getStore() are
|
|
776
777
|
// no longer guaranteed to be fresh, one-shot objects now that processLocalTransaction (#1591/
|
|
777
778
|
// #1592) installs one shared, long-lived context for the lifetime of an entire operation
|
|
@@ -795,7 +796,7 @@ function transactional(
|
|
|
795
796
|
// starting fresh) makes the write throw transactionOpenTooLongError via addWrite()/commit()'s
|
|
796
797
|
// poison check, correctly propagating the abort to the caller. See
|
|
797
798
|
// integrationTests/resources/txn-overtime-atomicity.test.ts.
|
|
798
|
-
if (context?.transaction
|
|
799
|
+
if (isJoinableScope(context?.transaction) || context?.transaction?.timedOut) {
|
|
799
800
|
// we are already in a transaction (or it was poisoned by a timeout abort and must fail), proceed
|
|
800
801
|
const resource = this.getResource(query, context, resourceOptions);
|
|
801
802
|
return resource.then
|
|
@@ -76,6 +76,11 @@ export interface Context {
|
|
|
76
76
|
* so there the completed transaction itself stays in the slot — also safe to call, but retained.
|
|
77
77
|
* `null` was the previous released marker and is still accepted defensively.
|
|
78
78
|
*
|
|
79
|
+
* A read that resolves a transaction without going through the static-API wrappers (an instance load,
|
|
80
|
+
* for one) replaces a released or never-set slot with an ImmediateTransaction, which commits every
|
|
81
|
+
* write as it is made. `transaction()` and the static API start their own scope rather than joining
|
|
82
|
+
* that, so an explicit `transaction()` is atomic on a released slot as it is on a fresh one.
|
|
83
|
+
*
|
|
79
84
|
* A transaction its own handler commits mid-scope is rotated to a fresh open generation, so the rest
|
|
80
85
|
* of that scope's writes are committed — or rolled back — with the scope's final commit rather than
|
|
81
86
|
* each committing itself immediately. Two cases keep the older per-write behavior: a commit made
|